Add the record alloc option.

This option adds the ability to record all of the allocation requests
and dump them to a file when a signal is sent to the process.

Included in this change, redo the option processing to add a new
string option.

Bug: 27747898

Change-Id: Ida043362e38b5eb1d459c99db9c2581015dab366
diff --git a/libc/malloc_debug/Android.mk b/libc/malloc_debug/Android.mk
index 3576611..00f5f89 100644
--- a/libc/malloc_debug/Android.mk
+++ b/libc/malloc_debug/Android.mk
@@ -8,6 +8,7 @@
     FreeTrackData.cpp \
     GuardData.cpp \
     malloc_debug.cpp \
+    RecordData.cpp \
     TrackData.cpp \
 
 # ==============================================================
@@ -58,6 +59,7 @@
 LOCAL_STATIC_LIBRARIES_arm := libunwind_llvm
 
 LOCAL_STATIC_LIBRARIES += \
+    libbase \
     libc_malloc_debug_backtrace \
     libc_logging \
 
diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp
index 6220a23..885542e 100644
--- a/libc/malloc_debug/Config.cpp
+++ b/libc/malloc_debug/Config.cpp
@@ -64,29 +64,126 @@
 static constexpr size_t DEFAULT_FREE_TRACK_ALLOCATIONS = 100;
 static constexpr size_t MAX_FREE_TRACK_ALLOCATIONS = 16384;
 
-struct Feature {
-  Feature(std::string name, size_t default_value, size_t min_value, size_t max_value,
-          uint64_t option, size_t* value, bool* config, bool combo_option)
-      : name(name), default_value(default_value), min_value(min_value), max_value(max_value),
-        option(option), value(value), config(config), combo_option(combo_option) {}
-  std::string name;
-  size_t default_value = 0;
-  size_t min_value = 0;
-  size_t max_value = 0;
+static constexpr size_t DEFAULT_RECORD_ALLOCS = 8000000;
+static constexpr size_t MAX_RECORD_ALLOCS = 50000000;
+static constexpr const char DEFAULT_RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt";
 
-  uint64_t option = 0;
-  size_t* value = nullptr;
-  bool* config = nullptr;
+struct Option {
+  Option(std::string name, uint64_t option, bool combo_option = false, bool* config = nullptr)
+      : name(name), option(option), combo_option(combo_option), config(config) {}
+  virtual ~Option() = default;
+
+  std::string name;
+
+  uint64_t option;
   // If set to true, then all of the options following are set on until
-  // for which the combo_option value is set.
+  // the combo_option value is set to false.
   bool combo_option = false;
+  bool* config;
+
+  virtual bool ParseValue(const std::string& option_name, const std::string& value) const;
+
+  virtual void SetDefault() const { }
 };
 
+bool Option::ParseValue(const std::string& option_name, const std::string& raw_value) const {
+  if (!raw_value.empty()) {
+    error_log("%s: value set for option '%s' which does not take a value",
+              getprogname(), option_name.c_str());
+    return false;
+  }
+  return true;
+}
+
+struct OptionString : public Option {
+  OptionString(std::string name, uint64_t option, std::string default_value,
+                std::string* value, bool combo_option = false,
+                bool* config = nullptr)
+      : Option(name, option, combo_option, config), default_value(default_value), value(value) {}
+  virtual ~OptionString() = default;
+
+  std::string default_value;
+  std::string* value;
+
+  bool ParseValue(const std::string& option_name, const std::string& value) const override;
+
+  void SetDefault() const override { if (value) *value = default_value; }
+};
+
+bool OptionString::ParseValue(const std::string&, const std::string& raw_value) const {
+  if (!raw_value.empty()) {
+    *value = raw_value;
+  }
+  return true;
+}
+
+struct OptionSizeT : public Option {
+  OptionSizeT(std::string name, size_t default_value, size_t min_value, size_t max_value,
+          uint64_t option, size_t* value, bool combo_option = false, bool* config = nullptr)
+      : Option(name, option, combo_option, config), default_value(default_value),
+        min_value(min_value), max_value(max_value), value(value) {}
+  virtual ~OptionSizeT() = default;
+
+  size_t default_value;
+  size_t min_value;
+  size_t max_value;
+
+  size_t* value;
+
+  bool ParseValue(const std::string& option_name, const std::string& value) const override;
+
+  void SetDefault() const override { if (value) *value = default_value; }
+};
+
+bool OptionSizeT::ParseValue(const std::string& option_name, const std::string& raw_value) const {
+  if (raw_value.empty()) {
+    // Value should have been set by the SetDefault() pass.
+    return true;
+  }
+
+  // Parse the value into a size_t value.
+  errno = 0;
+  char* end;
+  long parsed_value = strtol(raw_value.c_str(), &end, 10);
+  if (errno != 0) {
+    error_log("%s: bad value for option '%s': %s", getprogname(), option_name.c_str(),
+              strerror(errno));
+    return false;
+  }
+  if (end == raw_value.c_str()) {
+    error_log("%s: bad value for option '%s'", getprogname(), option_name.c_str());
+    return false;
+  }
+  if (static_cast<size_t>(end - raw_value.c_str()) != raw_value.size()) {
+    error_log("%s: bad value for option '%s', non space found after option: %s",
+              getprogname(), option_name.c_str(), end);
+    return false;
+  }
+  if (parsed_value < 0) {
+    error_log("%s: bad value for option '%s', value cannot be negative: %ld",
+              getprogname(), option_name.c_str(), parsed_value);
+    return false;
+  }
+
+  if (static_cast<size_t>(parsed_value) < min_value) {
+    error_log("%s: bad value for option '%s', value must be >= %zu: %ld",
+              getprogname(), option_name.c_str(), min_value, parsed_value);
+    return false;
+  }
+  if (static_cast<size_t>(parsed_value) > max_value) {
+    error_log("%s: bad value for option '%s', value must be <= %zu: %ld",
+              getprogname(), option_name.c_str(), max_value, parsed_value);
+    return false;
+  }
+  *value = static_cast<size_t>(parsed_value);
+  return true;
+}
+
 class PropertyParser {
  public:
   explicit PropertyParser(const char* property) : cur_(property) {}
 
-  bool Get(std::string* property, size_t* value, bool* value_set);
+  bool Get(std::string* property, std::string* value);
 
   bool Done() { return done_; }
 
@@ -100,7 +197,7 @@
   DISALLOW_COPY_AND_ASSIGN(PropertyParser);
 };
 
-bool PropertyParser::Get(std::string* property, size_t* value, bool* value_set) {
+bool PropertyParser::Get(std::string* property, std::string* value) {
   // Process each property name we can find.
   while (isspace(*cur_))
     ++cur_;
@@ -110,44 +207,30 @@
     return false;
   }
 
-  const char* property_start = cur_;
+  const char* start = cur_;
   while (!isspace(*cur_) && *cur_ != '=' && *cur_ != '\0')
     ++cur_;
 
-  *property = std::string(property_start, cur_ - property_start);
+  *property = std::string(start, cur_ - start);
 
   // Skip any spaces after the name.
-  while (isspace(*cur_) && *cur_ != '=' && *cur_ != '\0')
+  while (isspace(*cur_))
     ++cur_;
 
+  value->clear();
   if (*cur_ == '=') {
     ++cur_;
-    errno = 0;
-    *value_set = true;
-    char* end;
-    long read_value = strtol(cur_, const_cast<char**>(&end), 10);
-    if (errno != 0) {
-      error_log("%s: bad value for option '%s': %s", getprogname(), property->c_str(),
-                strerror(errno));
-      return false;
+    // Skip the space after the equal.
+    while (isspace(*cur_))
+      ++cur_;
+
+    start = cur_;
+    while (!isspace(*cur_) && *cur_ != '\0')
+      ++cur_;
+
+    if (cur_ != start) {
+      *value = std::string(start, cur_ - start);
     }
-    if (cur_ == end || (!isspace(*end) && *end != '\0')) {
-      if (cur_ == end) {
-        error_log("%s: bad value for option '%s'", getprogname(), property->c_str());
-      } else {
-        error_log("%s: bad value for option '%s', non space found after option: %s",
-                  getprogname(), property->c_str(), end);
-      }
-      return false;
-    } else if (read_value < 0) {
-      error_log("%s: bad value for option '%s', value cannot be negative: %ld",
-                getprogname(), property->c_str(), read_value);
-      return false;
-    }
-    *value = static_cast<size_t>(read_value);
-    cur_ = end;
-  } else {
-    *value_set = false;
   }
   return true;
 }
@@ -229,34 +312,19 @@
   error_log("");
   error_log("  leak_track");
   error_log("    Enable the leak tracking of memory allocations.");
-}
-
-static bool SetFeature(
-    const std::string name, const Feature& feature, size_t value, bool value_set) {
-  if (feature.config) {
-    *feature.config = true;
-  }
-  if (feature.value != nullptr) {
-    if (value_set) {
-      if (value < feature.min_value) {
-        error_log("%s: bad value for option '%s', value must be >= %zu: %zu",
-                  getprogname(), name.c_str(), feature.min_value, value);
-        return false;
-      } else if (value > feature.max_value) {
-        error_log("%s: bad value for option '%s', value must be <= %zu: %zu",
-                  getprogname(), name.c_str(), feature.max_value, value);
-        return false;
-      }
-      *feature.value = value;
-    } else {
-      *feature.value = feature.default_value;
-    }
-  } else if (value_set) {
-     error_log("%s: value set for option '%s' which does not take a value",
-               getprogname(), name.c_str());
-     return false;
-  }
-  return true;
+  error_log("");
+  error_log("  record_allocs[=XX]");
+  error_log("    Record every single allocation/free call. When a specific signal");
+  error_log("    is sent to the process, the contents of recording are written to");
+  error_log("    a file (%s) and the recording is cleared.", DEFAULT_RECORD_ALLOCS_FILE);
+  error_log("    If XX is set, that is the total number of allocations/frees that can");
+  error_log("    recorded. of frames to capture. The default value is %zu.", DEFAULT_RECORD_ALLOCS);
+  error_log("    If the allocation list fills up, all further allocations are not recorded.");
+  error_log("");
+  error_log("  record_alloc_file[=FILE]");
+  error_log("    This option only has meaning if the record_allocs options has been specified.");
+  error_log("    This is the name of the file to which recording information will be dumped.");
+  error_log("    The default is %s.", DEFAULT_RECORD_ALLOCS_FILE);
 }
 
 // This function is designed to be called once. A second call will not
@@ -274,88 +342,123 @@
   front_guard_value = DEFAULT_FRONT_GUARD_VALUE;
   rear_guard_value = DEFAULT_REAR_GUARD_VALUE;
   backtrace_signal = SIGRTMAX - 19;
-  free_track_backtrace_num_frames = 16;
+  record_allocs_signal = SIGRTMAX - 18;
+  free_track_backtrace_num_frames = 0;
+  record_allocs_file.clear();
 
   // Parse the options are of the format:
   //   option_name or option_name=XX
 
-  // Supported features:
-  const Feature features[] = {
-    Feature("guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, 0, nullptr, nullptr, true),
-    // Enable front guard. Value is the size of the guard.
-    Feature("front_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, FRONT_GUARD,
-            &this->front_guard_bytes, nullptr, true),
-    // Enable end guard. Value is the size of the guard.
-    Feature("rear_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, REAR_GUARD,
-            &this->rear_guard_bytes, nullptr, true),
+  // Supported options:
+  const OptionSizeT option_guard(
+      "guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, 0, nullptr, true);
+  // Enable front guard. Value is the size of the guard.
+  const OptionSizeT option_front_guard(
+      "front_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, FRONT_GUARD,
+      &this->front_guard_bytes, true);
+  // Enable end guard. Value is the size of the guard.
+  const OptionSizeT option_rear_guard(
+      "rear_guard", DEFAULT_GUARD_BYTES, 1, MAX_GUARD_BYTES, REAR_GUARD, &this->rear_guard_bytes,
+      true);
 
-    // Enable logging the backtrace on allocation. Value is the total
-    // number of frames to log.
-    Feature("backtrace", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
-            BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, &this->backtrace_enabled, false),
-    // Enable gathering backtrace values on a signal.
-    Feature("backtrace_enable_on_signal", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
-            BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, &this->backtrace_enable_on_signal,
-            false),
+  // Enable logging the backtrace on allocation. Value is the total
+  // number of frames to log.
+  const OptionSizeT option_backtrace(
+      "backtrace", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES, BACKTRACE | TRACK_ALLOCS,
+      &this->backtrace_frames, false, &this->backtrace_enabled);
+  // Enable gathering backtrace values on a signal.
+  const OptionSizeT option_backtrace_enable_on_signal(
+      "backtrace_enable_on_signal", DEFAULT_BACKTRACE_FRAMES, 1, MAX_BACKTRACE_FRAMES,
+      BACKTRACE | TRACK_ALLOCS, &this->backtrace_frames, false, &this->backtrace_enable_on_signal);
 
-    Feature("fill", SIZE_MAX, 1, SIZE_MAX, 0, nullptr, nullptr, true),
-    // Fill the allocation with an arbitrary pattern on allocation.
-    // Value is the number of bytes of the allocation to fill
-    // (default entire allocation).
-    Feature("fill_on_alloc", SIZE_MAX, 1, SIZE_MAX, FILL_ON_ALLOC, &this->fill_on_alloc_bytes,
-            nullptr, true),
-    // Fill the allocation with an arbitrary pattern on free.
-    // Value is the number of bytes of the allocation to fill
-    // (default entire allocation).
-    Feature("fill_on_free", SIZE_MAX, 1, SIZE_MAX, FILL_ON_FREE, &this->fill_on_free_bytes, nullptr, true),
+  const OptionSizeT option_fill("fill", SIZE_MAX, 1, SIZE_MAX, 0, nullptr, true);
+  // Fill the allocation with an arbitrary pattern on allocation.
+  // Value is the number of bytes of the allocation to fill
+  // (default entire allocation).
+  const OptionSizeT option_fill_on_alloc(
+      "fill_on_alloc", SIZE_MAX, 1, SIZE_MAX, FILL_ON_ALLOC, &this->fill_on_alloc_bytes, true);
+  // Fill the allocation with an arbitrary pattern on free.
+  // Value is the number of bytes of the allocation to fill
+  // (default entire allocation).
+  const OptionSizeT option_fill_on_free(
+      "fill_on_free", SIZE_MAX, 1, SIZE_MAX, FILL_ON_FREE, &this->fill_on_free_bytes, true);
 
-    // Expand the size of every alloc by this number bytes. Value is
-    // the total number of bytes to expand every allocation by.
-    Feature ("expand_alloc", DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, EXPAND_ALLOC,
-             &this->expand_alloc_bytes, nullptr, false),
+  // Expand the size of every alloc by this number bytes. Value is
+  // the total number of bytes to expand every allocation by.
+  const OptionSizeT option_expand_alloc(
+      "expand_alloc", DEFAULT_EXPAND_BYTES, 1, MAX_EXPAND_BYTES, EXPAND_ALLOC,
+      &this->expand_alloc_bytes);
 
-    // Keep track of the freed allocations and verify at a later date
-    // that they have not been used. Turning this on, also turns on
-    // fill on free.
-    Feature("free_track", DEFAULT_FREE_TRACK_ALLOCATIONS, 1, MAX_FREE_TRACK_ALLOCATIONS,
-            FREE_TRACK | FILL_ON_FREE, &this->free_track_allocations, nullptr, false),
-    // Number of backtrace frames to keep when free_track is enabled. If this
-    // value is set to zero, no backtrace will be kept.
-    Feature("free_track_backtrace_num_frames", DEFAULT_BACKTRACE_FRAMES,
-            0, MAX_BACKTRACE_FRAMES, 0, &this->free_track_backtrace_num_frames, nullptr, false),
+  // Keep track of the freed allocations and verify at a later date
+  // that they have not been used. Turning this on, also turns on
+  // fill on free.
+  const OptionSizeT option_free_track(
+      "free_track", DEFAULT_FREE_TRACK_ALLOCATIONS, 1, MAX_FREE_TRACK_ALLOCATIONS,
+      FREE_TRACK | FILL_ON_FREE, &this->free_track_allocations);
+  // Number of backtrace frames to keep when free_track is enabled. If this
+  // value is set to zero, no backtrace will be kept.
+  const OptionSizeT option_free_track_backtrace_num_frames(
+      "free_track_backtrace_num_frames", DEFAULT_BACKTRACE_FRAMES, 0, MAX_BACKTRACE_FRAMES, 0,
+      &this->free_track_backtrace_num_frames);
 
-    // Enable printing leaked allocations.
-    Feature("leak_track", 0, 0, 0, LEAK_TRACK | TRACK_ALLOCS, nullptr, nullptr, false),
+  // Enable printing leaked allocations.
+  const Option option_leak_track("leak_track", LEAK_TRACK | TRACK_ALLOCS);
+
+  const OptionSizeT option_record_allocs(
+      "record_allocs", DEFAULT_RECORD_ALLOCS, 1, MAX_RECORD_ALLOCS, RECORD_ALLOCS,
+      &this->record_allocs_num_entries);
+  const OptionString option_record_allocs_file(
+      "record_allocs_file", 0, DEFAULT_RECORD_ALLOCS_FILE, &this->record_allocs_file);
+
+  const Option* option_list[] = {
+    &option_guard, &option_front_guard, &option_rear_guard,
+    &option_backtrace, &option_backtrace_enable_on_signal,
+    &option_fill, &option_fill_on_alloc, &option_fill_on_free,
+    &option_expand_alloc,
+    &option_free_track, &option_free_track_backtrace_num_frames,
+    &option_leak_track,
+    &option_record_allocs, &option_record_allocs_file,
   };
 
+  // Set defaults for all of the options.
+  for (size_t i = 0; i < sizeof(option_list)/sizeof(Option*); i++) {
+    option_list[i]->SetDefault();
+  }
+
   // Process each property name we can find.
-  std::string property;
-  size_t value;
-  bool value_set;
   PropertyParser parser(property_str);
   bool valid = true;
-  while (valid && parser.Get(&property, &value, &value_set)) {
+  std::string property;
+  std::string value;
+  while (valid && parser.Get(&property, &value)) {
     bool found = false;
-    for (size_t i = 0; i < sizeof(features)/sizeof(Feature); i++) {
-      if (property == features[i].name) {
-        if (features[i].option == 0 && features[i].combo_option) {
+    for (size_t i = 0; i < sizeof(option_list)/sizeof(Option*); i++) {
+      if (property == option_list[i]->name) {
+        if (option_list[i]->option == 0 && option_list[i]->combo_option) {
+          const std::string* option_name = &option_list[i]->name;
           i++;
-          for (; i < sizeof(features)/sizeof(Feature) && features[i].combo_option; i++) {
-            if (!SetFeature(property, features[i], value, value_set)) {
+          for (; i < sizeof(option_list)/sizeof(Option*) && option_list[i]->combo_option; i++) {
+            if (!option_list[i]->ParseValue(*option_name, value)) {
               valid = false;
               break;
             }
-            options |= features[i].option;
+            if (option_list[i]->config) {
+              *option_list[i]->config = true;
+            }
+            options |= option_list[i]->option;
           }
           if (!valid) {
             break;
           }
         } else {
-          if (!SetFeature(property, features[i], value, value_set)) {
+          if (!option_list[i]->ParseValue(option_list[i]->name, value)) {
             valid = false;
             break;
           }
-          options |= features[i].option;
+          if (option_list[i]->config) {
+            *option_list[i]->config = true;
+          }
+          options |= option_list[i]->option;
         }
         found = true;
         break;
@@ -376,13 +479,6 @@
     if (options & FRONT_GUARD) {
       front_guard_bytes = BIONIC_ALIGN(front_guard_bytes, MINIMUM_ALIGNMENT_BYTES);
     }
-
-    // This situation can occur if the free_track option is specified and
-    // the fill_on_free option is not. In this case, indicate the whole
-    // allocation should be filled.
-    if ((options & FILL_ON_FREE) && fill_on_free_bytes == 0) {
-      fill_on_free_bytes = SIZE_MAX;
-    }
   } else {
     parser.LogUsage();
   }
diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h
index 3ee93b2..ac620ad 100644
--- a/libc/malloc_debug/Config.h
+++ b/libc/malloc_debug/Config.h
@@ -31,6 +31,8 @@
 
 #include <stdint.h>
 
+#include <string>
+
 constexpr uint64_t FRONT_GUARD = 0x1;
 constexpr uint64_t REAR_GUARD = 0x2;
 constexpr uint64_t BACKTRACE = 0x4;
@@ -40,6 +42,7 @@
 constexpr uint64_t FREE_TRACK = 0x40;
 constexpr uint64_t TRACK_ALLOCS = 0x80;
 constexpr uint64_t LEAK_TRACK = 0x100;
+constexpr uint64_t RECORD_ALLOCS = 0x200;
 
 // In order to guarantee posix compliance, set the minimum alignment
 // to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems.
@@ -71,6 +74,10 @@
   size_t free_track_allocations = 0;
   size_t free_track_backtrace_num_frames = 0;
 
+  int record_allocs_signal = 0;
+  size_t record_allocs_num_entries = 0;
+  std::string record_allocs_file;
+
   uint64_t options = 0;
   uint8_t fill_alloc_value;
   uint8_t fill_free_value;
diff --git a/libc/malloc_debug/DebugData.cpp b/libc/malloc_debug/DebugData.cpp
index 58cbbcb..fdc2810 100644
--- a/libc/malloc_debug/DebugData.cpp
+++ b/libc/malloc_debug/DebugData.cpp
@@ -77,6 +77,13 @@
     }
   }
 
+  if (config_.options & RECORD_ALLOCS) {
+    record.reset(new RecordData());
+    if (!record->Initialize(config_)) {
+      return false;
+    }
+  }
+
   if (config_.options & EXPAND_ALLOC) {
     extra_bytes_ += config_.expand_alloc_bytes;
   }
diff --git a/libc/malloc_debug/DebugData.h b/libc/malloc_debug/DebugData.h
index 7e55512..7e2df0c 100644
--- a/libc/malloc_debug/DebugData.h
+++ b/libc/malloc_debug/DebugData.h
@@ -41,6 +41,7 @@
 #include "FreeTrackData.h"
 #include "GuardData.h"
 #include "malloc_debug.h"
+#include "RecordData.h"
 #include "TrackData.h"
 
 class DebugData {
@@ -91,6 +92,7 @@
   std::unique_ptr<FrontGuardData> front_guard;
   std::unique_ptr<RearGuardData> rear_guard;
   std::unique_ptr<FreeTrackData> free_track;
+  std::unique_ptr<RecordData> record;
 
  private:
   size_t extra_bytes_ = 0;
diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md
index b3742cb..984430b 100644
--- a/libc/malloc_debug/README.md
+++ b/libc/malloc_debug/README.md
@@ -224,6 +224,108 @@
     04-15 12:35:33.305  7412  7412 E malloc_debug:           #02  pc 000a9e38  /system/lib/libc++.so
     04-15 12:35:33.305  7412  7412 E malloc_debug:           #03  pc 000a28a8  /system/lib/libc++.so
 
+### record\_allocs[=TOTAL\_ENTRIES]
+Keep track of every allocation/free made on every thread and dump them
+to a file when the signal SIGRTMAX - 18 (which is 46 on most Android devices)
+is received.
+
+If TOTAL\_ENTRIES is set, then it indicates the total number of
+allocation/free records that can be retained. If the number of records
+reaches the TOTAL\_ENTRIES value, then any further allocations/frees are
+not recorded. The default value is 8,000,000 and the maximum value this
+can be set to is 50,000,000.
+
+Once the signal is received, and the current records are written to the
+file, all current records are deleted. Any allocations/frees occuring while
+the data is being dumped to the file are ignored.
+
+**NOTE**: This option is not available until the O release of Android.
+
+The allocation data is written in a human readable format. Every line begins
+with the THREAD\_ID returned by gettid(), which is the thread that is making
+the allocation/free. If a new thread is created, no special line is added
+to the file. However, when a thread completes, a special entry is added to
+the file indicating this.
+
+The thread complete line is:
+
+**THREAD\_ID**: thread\_done 0x0
+
+Example:
+
+    187: thread_done 0x0
+
+Below is how each type of allocation/free call ends up in the file dump.
+
+pointer = malloc(size)
+
+**THREAD\_ID**: malloc pointer size
+
+Example:
+
+    186: malloc 0xb6038060 20
+
+free(pointer)
+
+**THREAD\_ID**: free pointer
+
+Example:
+
+    186: free 0xb6038060
+
+pointer = calloc(nmemb, size)
+
+**THREAD\_ID**: calloc pointer nmemb size
+
+Example:
+
+    186: calloc 0xb609f080 32 4
+
+new\_pointer = realloc(old\_pointer, size)
+
+**THREAD\_ID**: realloc new\_pointer old\_pointer size
+
+Example:
+
+    186: realloc 0xb609f080 0xb603e9a0 12
+
+pointer = memalign(alignment, size)
+
+**THREAD\_ID**: memalign pointer alignment size
+
+posix\_memalign(&pointer, alignment, size)
+
+**THREAD\_ID**: memalign pointer alignment size
+
+Example:
+
+    186: memalign 0x85423660 16 104
+
+pointer = valloc(size)
+
+**THREAD\_ID**: memalign pointer 4096 size
+
+Example:
+
+    186: memalign 0x85423660 4096 112
+
+pointer = pvalloc(size)
+
+**THREAD\_ID**: memalign pointer 4096 <b>SIZE\_ROUNDED\_UP\_TO\_4096</b>
+
+Example:
+
+    186: memalign 0x85423660 4096 8192
+
+### record\_allocs\_file[=FILE\_NAME]
+This option only has meaning if record\_allocs is set. It indicates the
+file where the recorded allocations will be found.
+
+If FILE\_NAME is set, then it indicates where the record allocation data
+will be placed.
+
+**NOTE**: This option is not available until the O release of Android.
+
 Additional Errors
 -----------------
 There are a few other error messages that might appear in the log.
diff --git a/libc/malloc_debug/RecordData.cpp b/libc/malloc_debug/RecordData.cpp
new file mode 100644
index 0000000..c0f3486
--- /dev/null
+++ b/libc/malloc_debug/RecordData.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdatomic.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <mutex>
+
+#include <android-base/stringprintf.h>
+
+#include "Config.h"
+#include "debug_disable.h"
+#include "debug_log.h"
+#include "DebugData.h"
+#include "RecordData.h"
+
+RecordEntry::RecordEntry() : tid_(gettid()) {
+}
+
+std::string ThreadCompleteEntry::GetString() const {
+  return android::base::StringPrintf("%d: thread_done 0x0\n", tid_);
+}
+
+AllocEntry::AllocEntry(void* pointer) : pointer_(pointer) {
+}
+
+MallocEntry::MallocEntry(void* pointer, size_t size) : AllocEntry(pointer), size_(size) {
+}
+
+std::string MallocEntry::GetString() const {
+  return android::base::StringPrintf("%d: malloc %p %zu\n", tid_, pointer_, size_);
+}
+
+FreeEntry::FreeEntry(void* pointer) : AllocEntry(pointer) {
+}
+
+std::string FreeEntry::GetString() const {
+  return android::base::StringPrintf("%d: free %p\n", tid_, pointer_);
+}
+
+CallocEntry::CallocEntry(void* pointer, size_t nmemb, size_t size)
+    : MallocEntry(pointer, size), nmemb_(nmemb) {
+}
+
+std::string CallocEntry::GetString() const {
+  return android::base::StringPrintf("%d: calloc %p %zu %zu\n", tid_, pointer_, nmemb_, size_);
+}
+
+ReallocEntry::ReallocEntry(void* pointer, size_t size, void* old_pointer)
+    : MallocEntry(pointer, size), old_pointer_(old_pointer) {
+}
+
+std::string ReallocEntry::GetString() const {
+  return android::base::StringPrintf("%d: realloc %p %p %zu\n", tid_, pointer_,
+                                     old_pointer_, size_);
+}
+
+// posix_memalign, memalgin, pvalloc, valloc all recorded with this class.
+MemalignEntry::MemalignEntry(void* pointer, size_t size, size_t alignment)
+    : MallocEntry(pointer, size), alignment_(alignment) {
+}
+
+std::string MemalignEntry::GetString() const {
+  return android::base::StringPrintf("%d: memalign %p %zu %zu\n", tid_, pointer_,
+                                     alignment_, size_);
+}
+
+struct ThreadData {
+  ThreadData(RecordData* record_data, ThreadCompleteEntry* entry) : record_data(record_data), entry(entry) {}
+  RecordData* record_data;
+  ThreadCompleteEntry* entry;
+  size_t count = 0;
+};
+
+static void ThreadKeyDelete(void* data) {
+  ThreadData* thread_data = reinterpret_cast<ThreadData*>(data);
+
+  thread_data->count++;
+
+  // This should be the last time we are called.
+  if (thread_data->count == 4) {
+    ScopedDisableDebugCalls disable;
+
+    thread_data->record_data->AddEntryOnly(thread_data->entry);
+    delete thread_data;
+  } else {
+    pthread_setspecific(thread_data->record_data->key(), data);
+  }
+}
+
+static void RecordDump(int, siginfo_t*, void*) {
+  // It's not necessarily safe to do the dump here, instead wait for the
+  // next allocation call to do the dump.
+  g_debug->record->SetToDump();
+}
+
+void RecordData::Dump() {
+  std::lock_guard<std::mutex> lock(dump_lock_);
+
+  // Make it so that no more entries can be added while dumping.
+  unsigned int last_entry_index = cur_index_.exchange(static_cast<unsigned int>(num_entries_));
+  if (dump_ == false) {
+    // Multiple Dump() calls from different threads, and we lost. Do nothing.
+    return;
+  }
+
+  // cur_index_ keeps getting incremented even if we hit the num_entries_.
+  // If that happens, cap the entries to dump by num_entries_.
+  if (last_entry_index > num_entries_) {
+    last_entry_index = num_entries_;
+  }
+
+  int dump_fd = open(dump_file_.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+                     0755);
+  if (dump_fd != -1) {
+    for (size_t i = 0; i < last_entry_index; i++) {
+      std::string line = entries_[i]->GetString();
+      ssize_t bytes = write(dump_fd, line.c_str(), line.length());
+      if (bytes == -1 || static_cast<size_t>(bytes) != line.length()) {
+        error_log("Failed to write record alloc information: %s", strerror(errno));
+        // Free all of the rest of the errors, we don't have any way
+        // to dump a partial list of the entries.
+        for (i++; i < last_entry_index; i++) {
+          delete entries_[i];
+          entries_[i] = nullptr;
+        }
+        break;
+      }
+      delete entries_[i];
+      entries_[i] = nullptr;
+    }
+    close(dump_fd);
+
+    // Mark the entries dumped.
+    cur_index_ = 0U;
+  } else {
+    error_log("Cannot create record alloc file %s: %s", dump_file_.c_str(), strerror(errno));
+    // Since we couldn't create the file, reset the entries dumped back
+    // to the original value.
+    cur_index_ = last_entry_index;
+  }
+
+  dump_ = false;
+}
+
+RecordData::RecordData() {
+  pthread_key_create(&key_, ThreadKeyDelete);
+}
+
+bool RecordData::Initialize(const Config& config) {
+  struct sigaction dump_act;
+  memset(&dump_act, 0, sizeof(dump_act));
+
+  dump_act.sa_sigaction = RecordDump;
+  dump_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
+  sigemptyset(&dump_act.sa_mask);
+  if (sigaction(config.record_allocs_signal, &dump_act, nullptr) != 0) {
+    error_log("Unable to set up record dump signal function: %s", strerror(errno));
+    return false;
+  }
+  pthread_setspecific(key_, nullptr);
+
+  info_log("%s: Run: 'kill -%d %d' to dump the allocation records.", getprogname(),
+           config.record_allocs_signal, getpid());
+
+  num_entries_ = config.record_allocs_num_entries;
+  entries_ = new const RecordEntry*[num_entries_];
+  cur_index_ = 0;
+  dump_ = false;
+  dump_file_ = config.record_allocs_file;
+
+  return true;
+}
+
+RecordData::~RecordData() {
+  delete [] entries_;
+  pthread_key_delete(key_);
+}
+
+void RecordData::AddEntryOnly(const RecordEntry* entry) {
+  unsigned int entry_index = cur_index_.fetch_add(1);
+  if (entry_index < num_entries_) {
+    entries_[entry_index] = entry;
+  }
+}
+
+void RecordData::AddEntry(const RecordEntry* entry) {
+  void* data = pthread_getspecific(key_);
+  if (data == nullptr) {
+    ThreadData* thread_data = new ThreadData(this, new ThreadCompleteEntry());
+    pthread_setspecific(key_, thread_data);
+  }
+
+  AddEntryOnly(entry);
+
+  // Check to see if it's time to dump the entries.
+  if (dump_) {
+    Dump();
+  }
+}
diff --git a/libc/malloc_debug/RecordData.h b/libc/malloc_debug/RecordData.h
new file mode 100644
index 0000000..741afd5
--- /dev/null
+++ b/libc/malloc_debug/RecordData.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *  * Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef DEBUG_MALLOC_RECORDDATA_H
+#define DEBUG_MALLOC_RECORDDATA_H
+
+#include <stdint.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <mutex>
+#include <string>
+
+#include <private/bionic_macros.h>
+
+class RecordEntry {
+ public:
+  RecordEntry();
+  virtual ~RecordEntry() = default;
+
+  virtual std::string GetString() const = 0;
+
+ protected:
+  pid_t tid_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(RecordEntry);
+};
+
+class ThreadCompleteEntry : public RecordEntry {
+ public:
+  ThreadCompleteEntry() = default;
+  virtual ~ThreadCompleteEntry() = default;
+
+  std::string GetString() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ThreadCompleteEntry);
+};
+
+class AllocEntry : public RecordEntry {
+ public:
+  AllocEntry(void* pointer);
+  virtual ~AllocEntry() = default;
+
+ protected:
+  void* pointer_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AllocEntry);
+};
+
+class MallocEntry : public AllocEntry {
+ public:
+  MallocEntry(void* pointer, size_t size);
+  virtual ~MallocEntry() = default;
+
+  std::string GetString() const override;
+
+ protected:
+  size_t size_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MallocEntry);
+};
+
+class FreeEntry : public AllocEntry {
+ public:
+  FreeEntry(void* pointer);
+  virtual ~FreeEntry() = default;
+
+  std::string GetString() const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(FreeEntry);
+};
+
+class CallocEntry : public MallocEntry {
+ public:
+  CallocEntry(void* pointer, size_t size, size_t nmemb);
+  virtual ~CallocEntry() = default;
+
+  std::string GetString() const override;
+
+ protected:
+  size_t nmemb_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CallocEntry);
+};
+
+class ReallocEntry : public MallocEntry {
+ public:
+  ReallocEntry(void* pointer, size_t size, void* old_pointer);
+  virtual ~ReallocEntry() = default;
+
+  std::string GetString() const override;
+
+ protected:
+  void* old_pointer_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ReallocEntry);
+};
+
+// posix_memalign, memalign, pvalloc, valloc all recorded with this class.
+class MemalignEntry : public MallocEntry {
+ public:
+  MemalignEntry(void* pointer, size_t size, size_t alignment);
+  virtual ~MemalignEntry() = default;
+
+  std::string GetString() const override;
+
+ protected:
+  size_t alignment_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MemalignEntry);
+};
+
+struct Config;
+
+class RecordData {
+ public:
+  RecordData();
+  virtual ~RecordData();
+
+  bool Initialize(const Config& config);
+
+  void AddEntry(const RecordEntry* entry);
+  void AddEntryOnly(const RecordEntry* entry);
+
+  void SetToDump() { dump_ = true; }
+
+  pthread_key_t key() { return key_; }
+
+ private:
+  void Dump();
+
+  std::mutex dump_lock_;
+  pthread_key_t key_;
+  const RecordEntry** entries_ = nullptr;
+  size_t num_entries_ = 0;
+  std::atomic_uint cur_index_;
+  std::atomic_bool dump_;
+  std::string dump_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(RecordData);
+};
+
+#endif // DEBUG_MALLOC_RECORDDATA_H
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index 5da5b88..d2bcf99 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -328,7 +328,13 @@
   }
   ScopedDisableDebugCalls disable;
 
-  return internal_malloc(size);
+  void* pointer = internal_malloc(size);
+
+  if (g_debug->config().options & RECORD_ALLOCS) {
+    g_debug->record->AddEntry(new MallocEntry(pointer, size));
+  }
+
+  return pointer;
 }
 
 static void internal_free(void* pointer) {
@@ -393,6 +399,10 @@
   }
   ScopedDisableDebugCalls disable;
 
+  if (g_debug->config().options & RECORD_ALLOCS) {
+    g_debug->record->AddEntry(new FreeEntry(pointer));
+  }
+
   internal_free(pointer);
 }
 
@@ -461,6 +471,10 @@
     memset(pointer, g_debug->config().fill_alloc_value, bytes);
   }
 
+  if (g_debug->config().options & RECORD_ALLOCS) {
+    g_debug->record->AddEntry(new MemalignEntry(pointer, bytes, alignment));
+  }
+
   return pointer;
 }
 
@@ -471,10 +485,18 @@
   ScopedDisableDebugCalls disable;
 
   if (pointer == nullptr) {
-    return internal_malloc(bytes);
+    pointer = internal_malloc(bytes);
+    if (g_debug->config().options & RECORD_ALLOCS) {
+      g_debug->record->AddEntry(new ReallocEntry(pointer, bytes, nullptr));
+    }
+    return pointer;
   }
 
   if (bytes == 0) {
+    if (g_debug->config().options & RECORD_ALLOCS) {
+      g_debug->record->AddEntry(new ReallocEntry(nullptr, bytes, pointer));
+    }
+
     internal_free(pointer);
     return nullptr;
   }
@@ -555,6 +577,10 @@
     }
   }
 
+  if (g_debug->config().options & RECORD_ALLOCS) {
+    g_debug->record->AddEntry(new ReallocEntry(new_pointer, bytes, pointer));
+  }
+
   return new_pointer;
 }
 
@@ -582,6 +608,7 @@
     return nullptr;
   }
 
+  void* pointer;
   if (g_debug->need_header()) {
     // The above check will guarantee the multiply will not overflow.
     if (size > Header::max_size()) {
@@ -596,10 +623,14 @@
       return nullptr;
     }
     memset(header, 0, g_dispatch->malloc_usable_size(header));
-    return InitHeader(header, header, size);
+    pointer = InitHeader(header, header, size);
   } else {
-    return g_dispatch->calloc(1, real_size);
+    pointer = g_dispatch->calloc(1, real_size);
   }
+  if (g_debug->config().options & RECORD_ALLOCS) {
+    g_debug->record->AddEntry(new CallocEntry(pointer, bytes, nmemb));
+  }
+  return pointer;
 }
 
 struct mallinfo debug_mallinfo() {
diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
index 85d5cb5..b8cf7cf 100644
--- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
@@ -113,6 +113,19 @@
   "6 malloc_debug \n"
   "6 malloc_debug   leak_track\n"
   "6 malloc_debug     Enable the leak tracking of memory allocations.\n"
+  "6 malloc_debug \n"
+  "6 malloc_debug   record_allocs[=XX]\n"
+  "6 malloc_debug     Record every single allocation/free call. When a specific signal\n"
+  "6 malloc_debug     is sent to the process, the contents of recording are written to\n"
+  "6 malloc_debug     a file (/data/local/tmp/record_allocs.txt) and the recording is cleared.\n"
+  "6 malloc_debug     If XX is set, that is the total number of allocations/frees that can\n"
+  "6 malloc_debug     recorded. of frames to capture. The default value is 8000000.\n"
+  "6 malloc_debug     If the allocation list fills up, all further allocations are not recorded.\n"
+  "6 malloc_debug \n"
+  "6 malloc_debug   record_alloc_file[=FILE]\n"
+  "6 malloc_debug     This option only has meaning if the record_allocs options has been specified.\n"
+  "6 malloc_debug     This is the name of the file to which recording information will be dumped.\n"
+  "6 malloc_debug     The default is /data/local/tmp/record_allocs.txt.\n"
 );
 
 TEST_F(MallocDebugConfigTest, unknown_option) {
@@ -190,7 +203,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, space_before_equal) {
-  ASSERT_TRUE(InitConfig("backtrace  =10"));
+  ASSERT_TRUE(InitConfig("backtrace  =10")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(10U, config->backtrace_frames);
 
@@ -199,7 +212,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, space_after_equal) {
-  ASSERT_TRUE(InitConfig("backtrace=  10"));
+  ASSERT_TRUE(InitConfig("backtrace=  10")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(10U, config->backtrace_frames);
 
@@ -208,7 +221,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, extra_space) {
-  ASSERT_TRUE(InitConfig("   backtrace=64   "));
+  ASSERT_TRUE(InitConfig("   backtrace=64   ")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(64U, config->backtrace_frames);
 
@@ -217,7 +230,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, multiple_options) {
-  ASSERT_TRUE(InitConfig("  backtrace=64   front_guard=48"));
+  ASSERT_TRUE(InitConfig("  backtrace=64   front_guard=48")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS | FRONT_GUARD, config->options);
   ASSERT_EQ(64U, config->backtrace_frames);
   ASSERT_EQ(48U, config->front_guard_bytes);
@@ -227,15 +240,15 @@
 }
 
 TEST_F(MallocDebugConfigTest, front_guard) {
-  ASSERT_TRUE(InitConfig("front_guard=48"));
+  ASSERT_TRUE(InitConfig("front_guard=48")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD, config->options);
   ASSERT_EQ(48U, config->front_guard_bytes);
 
-  ASSERT_TRUE(InitConfig("front_guard"));
+  ASSERT_TRUE(InitConfig("front_guard")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD, config->options);
   ASSERT_EQ(32U, config->front_guard_bytes);
 
-  ASSERT_TRUE(InitConfig("front_guard=39"));
+  ASSERT_TRUE(InitConfig("front_guard=39")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD, config->options);
 #if defined(__LP64__)
   ASSERT_EQ(48U, config->front_guard_bytes);
@@ -243,7 +256,7 @@
   ASSERT_EQ(40U, config->front_guard_bytes);
 #endif
 
-  ASSERT_TRUE(InitConfig("front_guard=41"));
+  ASSERT_TRUE(InitConfig("front_guard=41")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD, config->options);
   ASSERT_EQ(48U, config->front_guard_bytes);
 
@@ -252,11 +265,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, rear_guard) {
-  ASSERT_TRUE(InitConfig("rear_guard=50"));
+  ASSERT_TRUE(InitConfig("rear_guard=50")) << getFakeLogPrint();
   ASSERT_EQ(REAR_GUARD, config->options);
   ASSERT_EQ(50U, config->rear_guard_bytes);
 
-  ASSERT_TRUE(InitConfig("rear_guard"));
+  ASSERT_TRUE(InitConfig("rear_guard")) << getFakeLogPrint();
   ASSERT_EQ(REAR_GUARD, config->options);
   ASSERT_EQ(32U, config->rear_guard_bytes);
 
@@ -265,12 +278,12 @@
 }
 
 TEST_F(MallocDebugConfigTest, guard) {
-  ASSERT_TRUE(InitConfig("guard=32"));
+  ASSERT_TRUE(InitConfig("guard=32")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options);
   ASSERT_EQ(32U, config->front_guard_bytes);
   ASSERT_EQ(32U, config->rear_guard_bytes);
 
-  ASSERT_TRUE(InitConfig("guard"));
+  ASSERT_TRUE(InitConfig("guard")) << getFakeLogPrint();
   ASSERT_EQ(FRONT_GUARD | REAR_GUARD, config->options);
   ASSERT_EQ(32U, config->front_guard_bytes);
   ASSERT_EQ(32U, config->rear_guard_bytes);
@@ -280,11 +293,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, backtrace) {
-  ASSERT_TRUE(InitConfig("backtrace=64"));
+  ASSERT_TRUE(InitConfig("backtrace=64")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(64U, config->backtrace_frames);
 
-  ASSERT_TRUE(InitConfig("backtrace"));
+  ASSERT_TRUE(InitConfig("backtrace")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(16U, config->backtrace_frames);
 
@@ -293,11 +306,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, backtrace_enable_on_signal) {
-  ASSERT_TRUE(InitConfig("backtrace_enable_on_signal=64"));
+  ASSERT_TRUE(InitConfig("backtrace_enable_on_signal=64")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(64U, config->backtrace_frames);
 
-  ASSERT_TRUE(InitConfig("backtrace_enable_on_signal"));
+  ASSERT_TRUE(InitConfig("backtrace_enable_on_signal")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE | TRACK_ALLOCS, config->options);
   ASSERT_EQ(16U, config->backtrace_frames);
 
@@ -306,11 +319,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, fill_on_alloc) {
-  ASSERT_TRUE(InitConfig("fill_on_alloc=64"));
+  ASSERT_TRUE(InitConfig("fill_on_alloc=64")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_ALLOC, config->options);
   ASSERT_EQ(64U, config->fill_on_alloc_bytes);
 
-  ASSERT_TRUE(InitConfig("fill_on_alloc"));
+  ASSERT_TRUE(InitConfig("fill_on_alloc")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_ALLOC, config->options);
   ASSERT_EQ(SIZE_MAX, config->fill_on_alloc_bytes);
 
@@ -319,11 +332,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, fill_on_free) {
-  ASSERT_TRUE(InitConfig("fill_on_free=64"));
+  ASSERT_TRUE(InitConfig("fill_on_free=64")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_FREE, config->options);
   ASSERT_EQ(64U, config->fill_on_free_bytes);
 
-  ASSERT_TRUE(InitConfig("fill_on_free"));
+  ASSERT_TRUE(InitConfig("fill_on_free")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_FREE, config->options);
   ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
 
@@ -332,12 +345,12 @@
 }
 
 TEST_F(MallocDebugConfigTest, fill) {
-  ASSERT_TRUE(InitConfig("fill=64"));
+  ASSERT_TRUE(InitConfig("fill=64")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_ALLOC | FILL_ON_FREE, config->options);
   ASSERT_EQ(64U, config->fill_on_alloc_bytes);
   ASSERT_EQ(64U, config->fill_on_free_bytes);
 
-  ASSERT_TRUE(InitConfig("fill"));
+  ASSERT_TRUE(InitConfig("fill")) << getFakeLogPrint();
   ASSERT_EQ(FILL_ON_ALLOC | FILL_ON_FREE, config->options);
   ASSERT_EQ(SIZE_MAX, config->fill_on_alloc_bytes);
   ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
@@ -347,11 +360,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, expand_alloc) {
-  ASSERT_TRUE(InitConfig("expand_alloc=1234"));
+  ASSERT_TRUE(InitConfig("expand_alloc=1234")) << getFakeLogPrint();
   ASSERT_EQ(EXPAND_ALLOC, config->options);
   ASSERT_EQ(1234U, config->expand_alloc_bytes);
 
-  ASSERT_TRUE(InitConfig("expand_alloc"));
+  ASSERT_TRUE(InitConfig("expand_alloc")) << getFakeLogPrint();
   ASSERT_EQ(EXPAND_ALLOC, config->options);
   ASSERT_EQ(16U, config->expand_alloc_bytes);
 
@@ -360,13 +373,13 @@
 }
 
 TEST_F(MallocDebugConfigTest, free_track) {
-  ASSERT_TRUE(InitConfig("free_track=1234"));
+  ASSERT_TRUE(InitConfig("free_track=1234")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(1234U, config->free_track_allocations);
   ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
   ASSERT_EQ(16U, config->free_track_backtrace_num_frames);
 
-  ASSERT_TRUE(InitConfig("free_track"));
+  ASSERT_TRUE(InitConfig("free_track")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(100U, config->free_track_allocations);
   ASSERT_EQ(SIZE_MAX, config->fill_on_free_bytes);
@@ -377,13 +390,13 @@
 }
 
 TEST_F(MallocDebugConfigTest, free_track_and_fill_on_free) {
-  ASSERT_TRUE(InitConfig("free_track=1234 fill_on_free=32"));
+  ASSERT_TRUE(InitConfig("free_track=1234 fill_on_free=32")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(1234U, config->free_track_allocations);
   ASSERT_EQ(32U, config->fill_on_free_bytes);
   ASSERT_EQ(16U, config->free_track_backtrace_num_frames);
 
-  ASSERT_TRUE(InitConfig("free_track fill_on_free=60"));
+  ASSERT_TRUE(InitConfig("free_track fill_on_free=60")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(100U, config->free_track_allocations);
   ASSERT_EQ(60U, config->fill_on_free_bytes);
@@ -394,12 +407,12 @@
 }
 
 TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames) {
-  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=123"));
+  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=123")) << getFakeLogPrint();
 
   ASSERT_EQ(0U, config->options);
   ASSERT_EQ(123U, config->free_track_backtrace_num_frames);
 
-  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames"));
+  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames")) << getFakeLogPrint();
   ASSERT_EQ(0U, config->options);
   ASSERT_EQ(16U, config->free_track_backtrace_num_frames);
 
@@ -408,7 +421,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_zero) {
-  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=0"));
+  ASSERT_TRUE(InitConfig("free_track_backtrace_num_frames=0")) << getFakeLogPrint();
 
   ASSERT_EQ(0U, config->options);
   ASSERT_EQ(0U, config->free_track_backtrace_num_frames);
@@ -418,11 +431,11 @@
 }
 
 TEST_F(MallocDebugConfigTest, free_track_backtrace_num_frames_and_free_track) {
-  ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames=123"));
+  ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames=123")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(123U, config->free_track_backtrace_num_frames);
 
-  ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames"));
+  ASSERT_TRUE(InitConfig("free_track free_track_backtrace_num_frames")) << getFakeLogPrint();
   ASSERT_EQ(FREE_TRACK | FILL_ON_FREE, config->options);
   ASSERT_EQ(16U, config->free_track_backtrace_num_frames);
 
@@ -431,7 +444,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, leak_track) {
-  ASSERT_TRUE(InitConfig("leak_track"));
+  ASSERT_TRUE(InitConfig("leak_track")) << getFakeLogPrint();
   ASSERT_EQ(LEAK_TRACK | TRACK_ALLOCS, config->options);
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -439,7 +452,7 @@
 }
 
 TEST_F(MallocDebugConfigTest, leak_track_fail) {
-  ASSERT_FALSE(InitConfig("leak_track=100"));
+  ASSERT_FALSE(InitConfig("leak_track=100")) << getFakeLogPrint();
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   std::string log_msg(
@@ -448,6 +461,32 @@
   ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
 }
 
+TEST_F(MallocDebugConfigTest, record_allocs) {
+  ASSERT_TRUE(InitConfig("record_allocs=1234")) << getFakeLogPrint();
+  ASSERT_EQ(RECORD_ALLOCS, config->options);
+  ASSERT_EQ(1234U, config->record_allocs_num_entries);
+  ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
+
+  ASSERT_TRUE(InitConfig("record_allocs")) << getFakeLogPrint();
+  ASSERT_EQ(RECORD_ALLOCS, config->options);
+  ASSERT_EQ(8000000U, config->record_allocs_num_entries);
+  ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugConfigTest, record_allocs_file) {
+  ASSERT_TRUE(InitConfig("record_allocs=1234 record_allocs_file=/fake/file")) << getFakeLogPrint();
+  ASSERT_STREQ("/fake/file", config->record_allocs_file.c_str());
+
+  ASSERT_TRUE(InitConfig("record_allocs_file")) << getFakeLogPrint();
+  ASSERT_STREQ("/data/local/tmp/record_allocs.txt", config->record_allocs_file.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
 TEST_F(MallocDebugConfigTest, guard_min_error) {
   ASSERT_FALSE(InitConfig("guard=0"));
 
@@ -626,3 +665,23 @@
       "value must be <= 256: 400\n");
   ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
 }
+
+TEST_F(MallocDebugConfigTest, record_alloc_min_error) {
+  ASSERT_FALSE(InitConfig("record_allocs=0"));
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string log_msg(
+      "6 malloc_debug malloc_testing: bad value for option 'record_allocs', "
+      "value must be >= 1: 0\n");
+  ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugConfigTest, record_allocs_max_error) {
+  ASSERT_FALSE(InitConfig("record_allocs=100000000"));
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string log_msg(
+      "6 malloc_debug malloc_testing: bad value for option 'record_allocs', "
+      "value must be <= 50000000: 100000000\n");
+  ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
+}
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 014b913..edb03f6 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -30,6 +30,7 @@
 
 #include <gtest/gtest.h>
 
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 
 #include <private/bionic_macros.h>
@@ -79,12 +80,16 @@
   return offset;
 }
 
+static constexpr const char RECORD_ALLOCS_FILE[] = "/data/local/tmp/record_allocs.txt";
+
 class MallocDebugTest : public ::testing::Test {
  protected:
   void SetUp() override {
     initialized = false;
     resetLogs();
     backtrace_fake_clear_all();
+    // Delete the record data file if it exists.
+    unlink(RECORD_ALLOCS_FILE);
   }
 
   void TearDown() override {
@@ -1266,7 +1271,7 @@
   debug_free_malloc_leak_info(info);
 
   // Send the signal to enable.
-  ASSERT_TRUE(kill(getpid(), SIGRTMIN + 10) == 0);
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 19) == 0);
   sleep(1);
 
   pointer = debug_malloc(100);
@@ -1291,7 +1296,7 @@
   debug_free_malloc_leak_info(info);
 
   // Send the signal to disable.
-  ASSERT_TRUE(kill(getpid(), SIGRTMIN + 10) == 0);
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 19) == 0);
   sleep(1);
 
   pointer = debug_malloc(200);
@@ -1311,7 +1316,7 @@
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   std::string expected_log = android::base::StringPrintf(
       "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to enable backtracing.\n",
-      SIGRTMIN + 10, getpid());
+      SIGRTMAX - 19, getpid());
   ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
 }
 
@@ -1512,3 +1517,231 @@
   debug_free(pointer);
 }
 #endif
+
+void VerifyRecordAllocs() {
+  std::string expected;
+
+  void* pointer = debug_malloc(10);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  pointer = debug_calloc(1, 20);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: calloc %p 20 1\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  pointer = debug_realloc(nullptr, 30);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: realloc %p 0x0 30\n", getpid(), pointer);
+  void* old_pointer = pointer;
+  pointer = debug_realloc(pointer, 2048);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: realloc %p %p 2048\n", getpid(),
+                                          pointer, old_pointer);
+  debug_realloc(pointer, 0);
+  expected += android::base::StringPrintf("%d: realloc 0x0 %p 0\n", getpid(), pointer);
+
+  pointer = debug_memalign(16, 40);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: memalign %p 16 40\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  ASSERT_EQ(0, debug_posix_memalign(&pointer, 32, 50));
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: memalign %p 32 50\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
+  pointer = debug_pvalloc(60);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: memalign %p 4096 4096\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  pointer = debug_valloc(70);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: memalign %p 4096 70\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+#endif
+
+  // Dump all of the data accumulated so far.
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
+  sleep(1);
+
+  // This triggers the dumping.
+  pointer = debug_malloc(110);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 110\n", getpid(), pointer);
+
+  // Read all of the contents.
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
+
+  ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = android::base::StringPrintf(
+      "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
+      SIGRTMAX - 18, getpid());
+  ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
+
+  debug_free(pointer);
+}
+
+TEST_F(MallocDebugTest, record_allocs_no_header) {
+  Init("record_allocs");
+
+  VerifyRecordAllocs();
+}
+
+TEST_F(MallocDebugTest, record_allocs_with_header) {
+  Init("record_allocs front_guard");
+
+  VerifyRecordAllocs();
+}
+
+TEST_F(MallocDebugTest, record_allocs_max) {
+  Init("record_allocs=5");
+
+  std::string expected;
+
+  void* pointer = debug_malloc(10);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  pointer = debug_malloc(20);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 20\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  pointer = debug_malloc(1024);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 1024\n", getpid(), pointer);
+  debug_free(pointer);
+
+  // Dump all of the data accumulated so far.
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
+  sleep(1);
+
+  // This triggers the dumping.
+  pointer = debug_malloc(110);
+  ASSERT_TRUE(pointer != nullptr);
+
+  // Read all of the contents.
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
+
+  ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = android::base::StringPrintf(
+      "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
+      SIGRTMAX - 18, getpid());
+  ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
+
+  debug_free(pointer);
+}
+
+TEST_F(MallocDebugTest, record_allocs_thread_done) {
+  Init("record_allocs=5");
+
+  static pid_t tid = 0;
+  static void* pointer = nullptr;
+  std::thread thread([](){
+    tid = gettid();
+    pointer = debug_malloc(100);
+    write(0, pointer, 0);
+    debug_free(pointer);
+  });
+  thread.join();
+
+  std::string expected = android::base::StringPrintf("%d: malloc %p 100\n", tid, pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", tid, pointer);
+  expected += android::base::StringPrintf("%d: thread_done 0x0\n", tid);
+
+  // Dump all of the data accumulated so far.
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
+  sleep(1);
+
+  // This triggers the dumping.
+  pointer = debug_malloc(23);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 23\n", getpid(), pointer);
+
+  // Read all of the contents.
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
+
+  ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = android::base::StringPrintf(
+      "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
+      SIGRTMAX - 18, getpid());
+  ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
+
+  debug_free(pointer);
+}
+
+TEST_F(MallocDebugTest, record_allocs_file_name_fail) {
+  Init("record_allocs=5");
+
+  // Delete the special.txt file and create a symbolic link there to
+  // make sure the create file will fail.
+  unlink(RECORD_ALLOCS_FILE);
+
+  ASSERT_EQ(0, symlink("/data/local/tmp/does_not_exist", RECORD_ALLOCS_FILE));
+
+  std::string expected;
+
+  void* pointer = debug_malloc(10);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 10\n", getpid(), pointer);
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  // Dump all of the data accumulated so far.
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
+  sleep(1);
+
+  // This triggers the dumping.
+  pointer = debug_malloc(110);
+  ASSERT_TRUE(pointer != nullptr);
+  expected += android::base::StringPrintf("%d: malloc %p 110\n", getpid(), pointer);
+
+  // Read all of the contents.
+  std::string actual;
+  ASSERT_FALSE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
+
+  // Unlink the file so the next dump passes.
+  ASSERT_EQ(0, unlink(RECORD_ALLOCS_FILE));
+
+  // Dump all of the data accumulated so far.
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 18) == 0);
+  sleep(1);
+
+  // This triggers the dumping.
+  debug_free(pointer);
+  expected += android::base::StringPrintf("%d: free %p\n", getpid(), pointer);
+
+  ASSERT_TRUE(android::base::ReadFileToString(RECORD_ALLOCS_FILE, &actual));
+  ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = android::base::StringPrintf(
+      "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
+      SIGRTMAX - 18, getpid());
+  expected_log += android::base::StringPrintf(
+      "6 malloc_debug Cannot create record alloc file %s: Too many symbolic links encountered\n",
+      RECORD_ALLOCS_FILE);
+  ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
+}