Add support for signal dumping log stats.

Adding the new option log_allocator_stats_on_signal that will call
mallopt(M_LOG_STATS, 0), when a signal is sent to a process.

This call does not happen in the signal handler, but a variable
is set to trigger the mallopt call. The next time any allocation
call occurs, the mallopt call will be made.

Also, includes new unit tests for the new config option.

Test: All unit tests pass.
Test: Enabling the new feature and stop/start and sending the signal to
Test: a process and observing the log contains the memory stats.
Test: Did the above for a scudo and jemalloc based device.
Change-Id: Idcf6fca74036efb065b9c4cc037aaf9bcf0f139e
diff --git a/libc/malloc_debug/Android.bp b/libc/malloc_debug/Android.bp
index 24bb18a..83e4aa8 100644
--- a/libc/malloc_debug/Android.bp
+++ b/libc/malloc_debug/Android.bp
@@ -61,6 +61,7 @@
         "DebugData.cpp",
         "debug_disable.cpp",
         "GuardData.cpp",
+        "LogAllocatorStats.cpp",
         "malloc_debug.cpp",
         "PointerData.cpp",
         "RecordData.cpp",
diff --git a/libc/malloc_debug/Config.cpp b/libc/malloc_debug/Config.cpp
index 77d2f02..89a7ce7 100644
--- a/libc/malloc_debug/Config.cpp
+++ b/libc/malloc_debug/Config.cpp
@@ -204,6 +204,10 @@
         "check_unreachable_on_signal",
         {CHECK_UNREACHABLE_ON_SIGNAL, &Config::VerifyValueEmpty},
     },
+    {
+        "log_allocator_stats_on_signal",
+        {LOG_ALLOCATOR_STATS_ON_SIGNAL, &Config::VerifyValueEmpty},
+    },
 };
 
 bool Config::ParseValue(const std::string& option, const std::string& value, size_t min_value,
@@ -467,6 +471,7 @@
   backtrace_min_size_bytes_ = 0;
   backtrace_max_size_bytes_ = SIZE_MAX;
   check_unreachable_signal_ = SIGRTMAX - 16;
+  log_allocator_stats_signal_ = SIGRTMAX - 15;
 
   // Process each option name we can find.
   std::string option;
diff --git a/libc/malloc_debug/Config.h b/libc/malloc_debug/Config.h
index ef1d2a9..754970f 100644
--- a/libc/malloc_debug/Config.h
+++ b/libc/malloc_debug/Config.h
@@ -48,6 +48,7 @@
 constexpr uint64_t VERBOSE = 0x1000;
 constexpr uint64_t CHECK_UNREACHABLE_ON_SIGNAL = 0x2000;
 constexpr uint64_t BACKTRACE_SPECIFIC_SIZES = 0x4000;
+constexpr uint64_t LOG_ALLOCATOR_STATS_ON_SIGNAL = 0x8000;
 
 // In order to guarantee posix compliance, set the minimum alignment
 // to 8 bytes for 32 bit systems and 16 bytes for 64 bit systems.
@@ -100,6 +101,8 @@
 
   int check_unreachable_signal() const { return check_unreachable_signal_; }
 
+  int log_allocator_stats_signal() const { return log_allocator_stats_signal_; }
+
  private:
   struct OptionInfo {
     uint64_t option;
@@ -175,4 +178,5 @@
   uint8_t rear_guard_value_;
 
   int check_unreachable_signal_ = 0;
+  int log_allocator_stats_signal_ = 0;
 };
diff --git a/libc/malloc_debug/DebugData.cpp b/libc/malloc_debug/DebugData.cpp
index 44c4a10..885cc95 100644
--- a/libc/malloc_debug/DebugData.cpp
+++ b/libc/malloc_debug/DebugData.cpp
@@ -31,6 +31,7 @@
 #include "Config.h"
 #include "DebugData.h"
 #include "GuardData.h"
+#include "LogAllocatorStats.h"
 #include "PointerData.h"
 #include "debug_disable.h"
 #include "malloc_debug.h"
@@ -75,6 +76,13 @@
   if (config_.options() & EXPAND_ALLOC) {
     extra_bytes_ += config_.expand_alloc_bytes();
   }
+
+  if (config_.options() & LOG_ALLOCATOR_STATS_ON_SIGNAL) {
+    if (!LogAllocatorStats::Initialize(config_)) {
+      return false;
+    }
+  }
+
   return true;
 }
 
diff --git a/libc/malloc_debug/LogAllocatorStats.cpp b/libc/malloc_debug/LogAllocatorStats.cpp
new file mode 100644
index 0000000..6d1434e
--- /dev/null
+++ b/libc/malloc_debug/LogAllocatorStats.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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 <malloc.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "Config.h"
+#include "LogAllocatorStats.h"
+#include "debug_log.h"
+
+namespace LogAllocatorStats {
+
+static std::atomic_bool g_call_mallopt = {};
+
+static void CallMalloptLogStats(int, struct siginfo*, void*) {
+  g_call_mallopt = true;
+}
+
+void CheckIfShouldLog() {
+  bool expected = true;
+  if (g_call_mallopt.compare_exchange_strong(expected, false)) {
+    info_log("Logging allocator stats...");
+    if (mallopt(M_LOG_STATS, 0) == 0) {
+      error_log("mallopt(M_LOG_STATS, 0) call failed.");
+    }
+  }
+}
+
+bool Initialize(const Config& config) {
+  struct sigaction64 log_stats_act = {};
+  log_stats_act.sa_sigaction = CallMalloptLogStats;
+  log_stats_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
+  if (sigaction64(config.log_allocator_stats_signal(), &log_stats_act, nullptr) != 0) {
+    error_log("Unable to set up log allocator stats signal function: %s", strerror(errno));
+    return false;
+  }
+
+  if (config.options() & VERBOSE) {
+    info_log("%s: Run: 'kill -%d %d' to log allocator stats.", getprogname(),
+             config.log_allocator_stats_signal(), getpid());
+  }
+
+  return true;
+}
+
+}  // namespace LogAllocatorStats
diff --git a/libc/malloc_debug/LogAllocatorStats.h b/libc/malloc_debug/LogAllocatorStats.h
new file mode 100644
index 0000000..99e0738
--- /dev/null
+++ b/libc/malloc_debug/LogAllocatorStats.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+// Forward declarations
+class ConfigData;
+
+namespace LogAllocatorStats {
+
+bool Initialize(const Config& config);
+
+void CheckIfShouldLog();
+
+}  // namespace LogAllocatorStats
diff --git a/libc/malloc_debug/README.md b/libc/malloc_debug/README.md
index 37fafa7..4e39bed 100644
--- a/libc/malloc_debug/README.md
+++ b/libc/malloc_debug/README.md
@@ -340,6 +340,16 @@
     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
 
+### log\_allocator\_stats\_on\_signal
+As of Android V, this option will trigger a call to:
+
+    mallopt(M_LOG_STATS, 0);
+
+When a process receives the signal SIGRTMAX - 15 (which is 49 on Android
+devices). The mallopt call is not async safe and is not called from the
+signal handler directly. Instead, the next time any allocation call occurs,
+the mallopt is called.
+
 ### 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 Android devices)
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index b06ec9e..3731a5d 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -53,6 +53,7 @@
 
 #include "Config.h"
 #include "DebugData.h"
+#include "LogAllocatorStats.h"
 #include "Unreachable.h"
 #include "UnwindBacktrace.h"
 #include "backtrace.h"
@@ -517,11 +518,15 @@
 }
 
 static TimedResult InternalMalloc(size_t size) {
-  if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
+  uint64_t options = g_debug->config().options();
+  if ((options & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
     debug_dump_heap(android::base::StringPrintf(
                         "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid())
                         .c_str());
   }
+  if (options & LOG_ALLOCATOR_STATS_ON_SIGNAL) {
+    LogAllocatorStats::CheckIfShouldLog();
+  }
 
   if (size == 0) {
     size = 1;
@@ -593,11 +598,15 @@
 }
 
 static TimedResult InternalFree(void* pointer) {
-  if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
+  uint64_t options = g_debug->config().options();
+  if ((options & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
     debug_dump_heap(android::base::StringPrintf(
                         "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid())
                         .c_str());
   }
+  if (options & LOG_ALLOCATOR_STATS_ON_SIGNAL) {
+    LogAllocatorStats::CheckIfShouldLog();
+  }
 
   void* free_pointer = pointer;
   size_t bytes;
diff --git a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
index a911e76..84c9145 100644
--- a/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_config_tests.cpp
@@ -824,6 +824,24 @@
   ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
 }
 
+TEST_F(MallocDebugConfigTest, log_allocator_stats_on_signal) {
+  ASSERT_TRUE(InitConfig("log_allocator_stats_on_signal")) << getFakeLogPrint();
+  ASSERT_EQ(LOG_ALLOCATOR_STATS_ON_SIGNAL, config->options());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugConfigTest, trigger_log_allocator_stats_on_signal_fail) {
+  ASSERT_FALSE(InitConfig("log_allocator_stats_on_signal=200")) << getFakeLogPrint();
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string log_msg(
+      "6 malloc_debug malloc_testing: value set for option 'log_allocator_stats_on_signal' "
+      "which does not take a value\n");
+  ASSERT_STREQ((log_msg + usage_string).c_str(), getFakeLogPrint().c_str());
+}
+
 TEST_F(MallocDebugConfigTest, size) {
   ASSERT_TRUE(InitConfig("backtrace_size=37")) << getFakeLogPrint();
   ASSERT_EQ(BACKTRACE_SPECIFIC_SIZES, config->options());
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index bf3ed14..334dada 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -267,6 +267,9 @@
         "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to dump the allocation records.\n",
         SIGRTMAX - 18, getpid());
     expected_log += android::base::StringPrintf(
+        "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to log allocator stats.\n",
+        SIGRTMAX - 15, getpid());
+    expected_log += android::base::StringPrintf(
         "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to check for unreachable memory.\n",
         SIGRTMAX - 16, getpid());
   }
@@ -348,6 +351,16 @@
   ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
 }
 
+TEST_F(MallocDebugTest, verbose_log_allocator_stats_on_signal) {
+  Init("verbose log_allocator_stats_on_signal");
+
+  std::string expected_log = android::base::StringPrintf(
+      "4 malloc_debug malloc_testing: Run: 'kill -%d %d' to log allocator stats.\n", SIGRTMAX - 15,
+      getpid());
+  expected_log += "4 malloc_debug malloc_testing: malloc debug enabled\n";
+  ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
+}
+
 TEST_F(MallocDebugTest, fill_on_free) {
   Init("fill_on_free free_track free_track_backtrace_num_frames=0");
 
@@ -411,7 +424,8 @@
 TEST_F(MallocDebugTest, all_options) {
   Init(
       "guard backtrace backtrace_enable_on_signal fill expand_alloc free_track leak_track "
-      "record_allocs verify_pointers abort_on_error verbose check_unreachable_on_signal");
+      "record_allocs verify_pointers abort_on_error verbose check_unreachable_on_signal "
+      "log_allocator_stats_on_signal");
   VerifyAllocCalls(true);
 }
 
@@ -2781,6 +2795,27 @@
       getFakeLogPrint().c_str());
 }
 
+TEST_F(MallocDebugTest, log_allocator_stats_on_signal) {
+  Init("log_allocator_stats_on_signal");
+
+  ASSERT_TRUE(kill(getpid(), SIGRTMAX - 15) == 0);
+  sleep(1);
+
+  // The first unreachable check will pass.
+  void* pointer = debug_malloc(110);
+  ASSERT_TRUE(pointer != nullptr);
+  debug_free(pointer);
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  if (!running_with_hwasan()) {
+    // Do an exact match because the mallopt should not fail in normal operation.
+    ASSERT_STREQ("4 malloc_debug Logging allocator stats...\n", getFakeLogPrint().c_str());
+  } else {
+    // mallopt fails with hwasan, so just verify that the message is present.
+    ASSERT_MATCH(getFakeLogPrint(), "4 malloc_debug Logging allocator stats...\\n");
+  }
+}
+
 TEST_F(MallocDebugTest, backtrace_only_some_sizes_with_backtrace_size) {
   Init("leak_track backtrace backtrace_size=120");