Refactor the malloc_info code.

malloc_info needs to be per native allocator, but the code treated it
like a global function that doesn't depend on the native memory allocator.

Update malloc debug to dump the actual pointers that it has been tracking.

Test: bionic-unit-tests pass.
Test: malloc debug tests pass.
Test: malloc hook tests pass.
Change-Id: I3b0d4d748489dd84c16d16933479dc8b8d79013e
Merged-In: I3b0d4d748489dd84c16d16933479dc8b8d79013e
(cherry picked from commit a3656a98b10d2a4a6194a5d9705ad9c2cc5877b0)
diff --git a/libc/Android.bp b/libc/Android.bp
index 669ffc0..0e902c1 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1089,7 +1089,6 @@
         "bionic/locale.cpp",
         "bionic/lockf.cpp",
         "bionic/lstat.cpp",
-        "bionic/malloc_info.cpp",
         "bionic/mblen.cpp",
         "bionic/mbrtoc16.cpp",
         "bionic/mbrtoc32.cpp",
diff --git a/libc/bionic/jemalloc.h b/libc/bionic/jemalloc.h
index 3fd36e4..b9a4e99 100644
--- a/libc/bionic/jemalloc.h
+++ b/libc/bionic/jemalloc.h
@@ -34,6 +34,7 @@
 struct mallinfo je_mallinfo();
 void je_malloc_disable();
 void je_malloc_enable();
+int je_malloc_info(int options, FILE* fp);
 int je_mallopt(int, int);
 void* je_memalign_round_up_boundary(size_t, size_t);
 void* je_pvalloc(size_t);
diff --git a/libc/bionic/jemalloc_wrapper.cpp b/libc/bionic/jemalloc_wrapper.cpp
index c513246..bc3a9dc 100644
--- a/libc/bionic/jemalloc_wrapper.cpp
+++ b/libc/bionic/jemalloc_wrapper.cpp
@@ -19,8 +19,9 @@
 #include <sys/param.h>
 #include <unistd.h>
 
+#include <private/MallocXmlElem.h>
+
 #include "jemalloc.h"
-#include "private/bionic_macros.h"
 
 void* je_pvalloc(size_t bytes) {
   size_t pagesize = getpagesize();
@@ -116,3 +117,49 @@
   }
   return 0;
 }
+
+__BEGIN_DECLS
+
+size_t __mallinfo_narenas();
+size_t __mallinfo_nbins();
+struct mallinfo __mallinfo_arena_info(size_t);
+struct mallinfo __mallinfo_bin_info(size_t, size_t);
+
+__END_DECLS
+
+int je_malloc_info(int options, FILE* fp) {
+  if (options != 0) {
+    errno = EINVAL;
+    return -1;
+  }
+
+  MallocXmlElem root(fp, "malloc", "version=\"jemalloc-1\"");
+
+  // Dump all of the large allocations in the arenas.
+  for (size_t i = 0; i < __mallinfo_narenas(); i++) {
+    struct mallinfo mi = __mallinfo_arena_info(i);
+    if (mi.hblkhd != 0) {
+      MallocXmlElem arena_elem(fp, "heap", "nr=\"%d\"", i);
+      {
+        MallocXmlElem(fp, "allocated-large").Contents("%zu", mi.ordblks);
+        MallocXmlElem(fp, "allocated-huge").Contents("%zu", mi.uordblks);
+        MallocXmlElem(fp, "allocated-bins").Contents("%zu", mi.fsmblks);
+
+        size_t total = 0;
+        for (size_t j = 0; j < __mallinfo_nbins(); j++) {
+          struct mallinfo mi = __mallinfo_bin_info(i, j);
+          if (mi.ordblks != 0) {
+            MallocXmlElem bin_elem(fp, "bin", "nr=\"%d\"", j);
+            MallocXmlElem(fp, "allocated").Contents("%zu", mi.ordblks);
+            MallocXmlElem(fp, "nmalloc").Contents("%zu", mi.uordblks);
+            MallocXmlElem(fp, "ndalloc").Contents("%zu", mi.fordblks);
+            total += mi.ordblks;
+          }
+        }
+        MallocXmlElem(fp, "bins-total").Contents("%zu", total);
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index bb8ec59..edd697d 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -42,6 +42,7 @@
 //   write_malloc_leak_info: Writes the leak info data to a file.
 
 #include <stdint.h>
+#include <stdio.h>
 
 #include <private/bionic_config.h>
 
@@ -93,6 +94,14 @@
   return Malloc(mallinfo)();
 }
 
+extern "C" int malloc_info(int options, FILE* fp) {
+  auto dispatch_table = GetDispatchTable();
+  if (__predict_false(dispatch_table != nullptr)) {
+    return dispatch_table->malloc_info(options, fp);
+  }
+  return Malloc(malloc_info)(options, fp);
+}
+
 extern "C" int mallopt(int param, int value) {
   auto dispatch_table = GetDispatchTable();
   if (__predict_false(dispatch_table != nullptr)) {
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index ce3e761..9656718 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -85,6 +85,7 @@
     Malloc(malloc_enable),
     Malloc(mallopt),
     Malloc(aligned_alloc),
+    Malloc(malloc_info),
   };
 
 static constexpr char kHooksSharedLib[] = "libc_malloc_hooks.so";
@@ -146,6 +147,10 @@
   if (!InitMallocFunction<MallocMalloc>(impl_handler, &table->malloc, prefix, "malloc")) {
     return false;
   }
+  if (!InitMallocFunction<MallocMallocInfo>(impl_handler, &table->malloc_info, prefix,
+                                                "malloc_info")) {
+    return false;
+  }
   if (!InitMallocFunction<MallocMallocUsableSize>(impl_handler, &table->malloc_usable_size, prefix,
                                                   "malloc_usable_size")) {
     return false;
diff --git a/libc/bionic/malloc_heapprofd.cpp b/libc/bionic/malloc_heapprofd.cpp
index 5129cd7..9cab67a 100644
--- a/libc/bionic/malloc_heapprofd.cpp
+++ b/libc/bionic/malloc_heapprofd.cpp
@@ -108,6 +108,7 @@
     Malloc(malloc_enable),
     Malloc(mallopt),
     Malloc(aligned_alloc),
+    Malloc(malloc_info),
   };
 
 static void MaybeInstallInitHeapprofdHook(int) {
diff --git a/libc/bionic/malloc_info.cpp b/libc/bionic/malloc_info.cpp
deleted file mode 100644
index 9c8a4bf..0000000
--- a/libc/bionic/malloc_info.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "malloc_info.h"
-
-#include <errno.h>
-#include "private/bionic_macros.h"
-
-class __LIBC_HIDDEN__ Elem {
-public:
-  // name must be valid throughout lifetime of the object.
-  explicit Elem(FILE* fp, const char* name,
-                const char* attr_fmt = nullptr, ...) {
-    this->fp = fp;
-    this->name = name;
-
-    fprintf(fp, "<%s", name);
-    if (attr_fmt != nullptr) {
-      va_list args;
-      va_start(args, attr_fmt);
-      fputc(' ', fp);
-      vfprintf(fp, attr_fmt, args);
-      va_end(args);
-    }
-    fputc('>', fp);
-  }
-
-  ~Elem() noexcept {
-    fprintf(fp, "</%s>", name);
-  }
-
-  void contents(const char* fmt, ...) {
-      va_list args;
-      va_start(args, fmt);
-      vfprintf(fp, fmt, args);
-      va_end(args);
-  }
-
-private:
-  FILE* fp;
-  const char* name;
-
-  BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(Elem);
-};
-
-int malloc_info(int options, FILE* fp) {
-  if (options != 0) {
-    errno = EINVAL;
-    return -1;
-  }
-
-  Elem root(fp, "malloc", "version=\"jemalloc-1\"");
-
-  // Dump all of the large allocations in the arenas.
-  for (size_t i = 0; i < __mallinfo_narenas(); i++) {
-    struct mallinfo mi = __mallinfo_arena_info(i);
-    if (mi.hblkhd != 0) {
-      Elem arena_elem(fp, "heap", "nr=\"%d\"", i);
-      {
-        Elem(fp, "allocated-large").contents("%zu", mi.ordblks);
-        Elem(fp, "allocated-huge").contents("%zu", mi.uordblks);
-        Elem(fp, "allocated-bins").contents("%zu", mi.fsmblks);
-
-        size_t total = 0;
-        for (size_t j = 0; j < __mallinfo_nbins(); j++) {
-          struct mallinfo mi = __mallinfo_bin_info(i, j);
-          if (mi.ordblks != 0) {
-            Elem bin_elem(fp, "bin", "nr=\"%d\"", j);
-            Elem(fp, "allocated").contents("%zu", mi.ordblks);
-            Elem(fp, "nmalloc").contents("%zu", mi.uordblks);
-            Elem(fp, "ndalloc").contents("%zu", mi.fordblks);
-            total += mi.ordblks;
-          }
-        }
-        Elem(fp, "bins-total").contents("%zu", total);
-      }
-    }
-  }
-
-  return 0;
-}
diff --git a/libc/bionic/malloc_info.h b/libc/bionic/malloc_info.h
deleted file mode 100644
index 257fec1..0000000
--- a/libc/bionic/malloc_info.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <malloc.h>
-#include <sys/cdefs.h>
-
-__BEGIN_DECLS
-
-__LIBC_HIDDEN__ size_t __mallinfo_narenas();
-__LIBC_HIDDEN__ size_t __mallinfo_nbins();
-__LIBC_HIDDEN__ struct mallinfo __mallinfo_arena_info(size_t);
-__LIBC_HIDDEN__ struct mallinfo __mallinfo_bin_info(size_t, size_t);
-
-__END_DECLS
diff --git a/libc/malloc_debug/Android.bp b/libc/malloc_debug/Android.bp
index 0961a94..f808d0c 100644
--- a/libc/malloc_debug/Android.bp
+++ b/libc/malloc_debug/Android.bp
@@ -123,6 +123,7 @@
     static_libs: [
         "libc_malloc_debug",
         "libdemangle",
+        "libtinyxml2",
     ],
 
     shared_libs: [
diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp
index 6e9d24f..5542c1e 100644
--- a/libc/malloc_debug/PointerData.cpp
+++ b/libc/malloc_debug/PointerData.cpp
@@ -492,6 +492,17 @@
   }
 }
 
+void PointerData::GetAllocList(std::vector<ListInfoType>* list) {
+  std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
+  std::lock_guard<std::mutex> frame_guard(frame_mutex_);
+
+  if (pointers_.empty()) {
+    return;
+  }
+
+  GetList(list, false);
+}
+
 void PointerData::GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size,
                           size_t* total_memory, size_t* backtrace_size) {
   std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h
index 6955c9a..24ca748 100644
--- a/libc/malloc_debug/PointerData.h
+++ b/libc/malloc_debug/PointerData.h
@@ -132,9 +132,6 @@
   void PostForkParent();
   void PostForkChild();
 
-  static void GetList(std::vector<ListInfoType>* list, bool only_with_backtrace);
-  static void GetUniqueList(std::vector<ListInfoType>* list, bool only_with_backtrace);
-
   static size_t AddBacktrace(size_t num_frames);
   static void RemoveBacktrace(size_t hash_index);
 
@@ -151,6 +148,7 @@
   static void VerifyFreedPointer(const FreePointerInfoType& info);
   static void VerifyAllFreed();
 
+  static void GetAllocList(std::vector<ListInfoType>* list);
   static void LogLeaks();
   static void DumpLiveToFile(FILE* fp);
 
@@ -165,6 +163,9 @@
   static std::string GetHashString(uintptr_t* frames, size_t num_frames);
   static void LogBacktrace(size_t hash_index);
 
+  static void GetList(std::vector<ListInfoType>* list, bool only_with_backtrace);
+  static void GetUniqueList(std::vector<ListInfoType>* list, bool only_with_backtrace);
+
   size_t alloc_offset_ = 0;
   std::vector<uint8_t> cmp_mem_;
 
diff --git a/libc/malloc_debug/exported32.map b/libc/malloc_debug/exported32.map
index 2f590d0..8ed37fa 100644
--- a/libc/malloc_debug/exported32.map
+++ b/libc/malloc_debug/exported32.map
@@ -14,6 +14,7 @@
     debug_malloc_backtrace;
     debug_malloc_disable;
     debug_malloc_enable;
+    debug_malloc_info;
     debug_malloc_usable_size;
     debug_mallopt;
     debug_memalign;
diff --git a/libc/malloc_debug/exported64.map b/libc/malloc_debug/exported64.map
index 08d36a5..cdff88b 100644
--- a/libc/malloc_debug/exported64.map
+++ b/libc/malloc_debug/exported64.map
@@ -14,6 +14,7 @@
     debug_malloc_backtrace;
     debug_malloc_disable;
     debug_malloc_enable;
+    debug_malloc_info;
     debug_malloc_usable_size;
     debug_mallopt;
     debug_memalign;
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index f662957..093bdee 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -29,6 +29,7 @@
 #include <errno.h>
 #include <inttypes.h>
 #include <malloc.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/cdefs.h>
 #include <sys/param.h>
@@ -41,6 +42,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <private/bionic_malloc_dispatch.h>
+#include <private/MallocXmlElem.h>
 
 #include "Config.h"
 #include "DebugData.h"
@@ -85,6 +87,7 @@
 void* debug_calloc(size_t nmemb, size_t bytes);
 struct mallinfo debug_mallinfo();
 int debug_mallopt(int param, int value);
+int debug_malloc_info(int options, FILE* fp);
 int debug_posix_memalign(void** memptr, size_t alignment, size_t size);
 int debug_iterate(uintptr_t base, size_t size,
                   void (*callback)(uintptr_t base, size_t size, void* arg), void* arg);
@@ -725,6 +728,32 @@
   return g_dispatch->mallopt(param, value);
 }
 
+int debug_malloc_info(int options, FILE* fp) {
+  if (DebugCallsDisabled() || !g_debug->TrackPointers()) {
+    return g_dispatch->malloc_info(options, fp);
+  }
+
+  MallocXmlElem root(fp, "malloc", "version=\"debug-malloc-1\"");
+  std::vector<ListInfoType> list;
+  PointerData::GetAllocList(&list);
+
+  size_t alloc_num = 0;
+  for (size_t i = 0; i < list.size(); i++) {
+    MallocXmlElem alloc(fp, "allocation", "nr=\"%zu\"", alloc_num);
+
+    size_t total = 1;
+    size_t size = list[i].size;
+    while (i < list.size() - 1 && list[i + 1].size == size) {
+      i++;
+      total++;
+    }
+    MallocXmlElem(fp, "size").Contents("%zu", list[i].size);
+    MallocXmlElem(fp, "total").Contents("%zu", total);
+    alloc_num++;
+  }
+  return 0;
+}
+
 void* debug_aligned_alloc(size_t alignment, size_t size) {
   if (DebugCallsDisabled()) {
     return g_dispatch->aligned_alloc(alignment, size);
@@ -741,7 +770,7 @@
     return g_dispatch->posix_memalign(memptr, alignment, size);
   }
 
-  if (!powerof2(alignment)) {
+  if (alignment < sizeof(void*) || !powerof2(alignment)) {
     return EINVAL;
   }
   int saved_errno = errno;
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index a72db3b..66955db 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -16,6 +16,7 @@
 
 #include <malloc.h>
 #include <signal.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/cdefs.h>
@@ -25,10 +26,13 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <memory>
 #include <thread>
 #include <vector>
 #include <utility>
 
+#include <tinyxml2.h>
+
 #include <gtest/gtest.h>
 
 #include <android-base/file.h>
@@ -62,6 +66,7 @@
 
 struct mallinfo debug_mallinfo();
 int debug_mallopt(int, int);
+int debug_malloc_info(int, FILE*);
 
 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
 void* debug_pvalloc(size_t);
@@ -136,6 +141,7 @@
   nullptr,
   mallopt,
   aligned_alloc,
+  malloc_info,
 };
 
 std::string ShowDiffs(uint8_t* a, uint8_t* b, size_t size) {
@@ -2465,3 +2471,82 @@
   pointer[-get_tag_offset()] = tag_value;
 }
 
+TEST_F(MallocDebugTest, malloc_info_no_pointer_tracking) {
+  Init("fill");
+
+  char* buffer;
+  size_t size;
+  FILE* memstream = open_memstream(&buffer, &size);
+  ASSERT_TRUE(memstream != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, memstream));
+  ASSERT_EQ(0, fclose(memstream));
+
+  tinyxml2::XMLDocument doc;
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  auto root = doc.FirstChildElement();
+  ASSERT_TRUE(root != nullptr);
+  ASSERT_STREQ("malloc", root->Name());
+  // Don't care what the underyling implementation says, just that it's
+  // not generated by debug malloc.
+  ASSERT_STRNE("debug-malloc-1", root->Attribute("version"));
+}
+
+TEST_F(MallocDebugTest, malloc_info_with_pointer_tracking) {
+  Init("verify_pointers");
+
+  std::unique_ptr<void, decltype(debug_free)*> ptr1(debug_malloc(1000), debug_free);
+  ASSERT_TRUE(ptr1.get() != nullptr);
+  std::unique_ptr<void, decltype(debug_free)*> ptr2(debug_malloc(1000), debug_free);
+  ASSERT_TRUE(ptr2.get() != nullptr);
+  std::unique_ptr<void, decltype(debug_free)*> ptr3(debug_malloc(500), debug_free);
+  ASSERT_TRUE(ptr3.get() != nullptr);
+  std::unique_ptr<void, decltype(debug_free)*> ptr4(debug_malloc(1200), debug_free);
+  ASSERT_TRUE(ptr4.get() != nullptr);
+
+  char* buffer;
+  size_t size;
+  FILE* memstream = open_memstream(&buffer, &size);
+  ASSERT_TRUE(memstream != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, memstream));
+  ASSERT_EQ(0, fclose(memstream));
+
+  SCOPED_TRACE(testing::Message() << "Output:\n" << buffer);
+
+  tinyxml2::XMLDocument doc;
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  auto root = doc.FirstChildElement();
+  ASSERT_TRUE(root != nullptr);
+  ASSERT_STREQ("malloc", root->Name());
+  ASSERT_STREQ("debug-malloc-1", root->Attribute("version"));
+
+  auto alloc = root->FirstChildElement();
+  ASSERT_TRUE(alloc != nullptr);
+  ASSERT_STREQ("allocation", alloc->Name());
+  int val;
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->QueryIntAttribute("nr", &val));
+  ASSERT_EQ(0, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("size")->QueryIntText(&val));
+  ASSERT_EQ(1200, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("total")->QueryIntText(&val));
+  ASSERT_EQ(1, val);
+
+  alloc = alloc->NextSiblingElement();
+  ASSERT_TRUE(alloc != nullptr);
+  ASSERT_STREQ("allocation", alloc->Name());
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->QueryIntAttribute("nr", &val));
+  ASSERT_EQ(1, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("size")->QueryIntText(&val));
+  ASSERT_EQ(1000, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("total")->QueryIntText(&val));
+  ASSERT_EQ(2, val);
+
+  alloc = alloc->NextSiblingElement();
+  ASSERT_TRUE(alloc != nullptr);
+  ASSERT_STREQ("allocation", alloc->Name());
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->QueryIntAttribute("nr", &val));
+  ASSERT_EQ(2, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("size")->QueryIntText(&val));
+  ASSERT_EQ(500, val);
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("total")->QueryIntText(&val));
+  ASSERT_EQ(1, val);
+}
diff --git a/libc/malloc_hooks/Android.bp b/libc/malloc_hooks/Android.bp
index d4b5a2a..d119f89 100644
--- a/libc/malloc_hooks/Android.bp
+++ b/libc/malloc_hooks/Android.bp
@@ -39,14 +39,6 @@
 // ==============================================================
 cc_test {
     name: "malloc_hooks_unit_tests",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
 
     srcs: [
         "tests/malloc_hooks_tests.cpp",
diff --git a/libc/malloc_hooks/exported32.map b/libc/malloc_hooks/exported32.map
index bd3e547..293d9ac 100644
--- a/libc/malloc_hooks/exported32.map
+++ b/libc/malloc_hooks/exported32.map
@@ -13,6 +13,7 @@
     hooks_malloc_backtrace;
     hooks_malloc_disable;
     hooks_malloc_enable;
+    hooks_malloc_info;
     hooks_malloc_usable_size;
     hooks_mallopt;
     hooks_memalign;
diff --git a/libc/malloc_hooks/exported64.map b/libc/malloc_hooks/exported64.map
index 72465c4..340106b 100644
--- a/libc/malloc_hooks/exported64.map
+++ b/libc/malloc_hooks/exported64.map
@@ -13,6 +13,7 @@
     hooks_malloc_backtrace;
     hooks_malloc_disable;
     hooks_malloc_enable;
+    hooks_malloc_info;
     hooks_malloc_usable_size;
     hooks_mallopt;
     hooks_memalign;
diff --git a/libc/malloc_hooks/malloc_hooks.cpp b/libc/malloc_hooks/malloc_hooks.cpp
index 715a629..07b668f 100644
--- a/libc/malloc_hooks/malloc_hooks.cpp
+++ b/libc/malloc_hooks/malloc_hooks.cpp
@@ -29,6 +29,7 @@
 #include <errno.h>
 #include <malloc.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <string.h>
 #include <sys/param.h>
 #include <unistd.h>
@@ -57,6 +58,7 @@
 void hooks_free_malloc_leak_info(uint8_t* info);
 size_t hooks_malloc_usable_size(void* pointer);
 void* hooks_malloc(size_t size);
+int hooks_malloc_info(int options, FILE* fp);
 void hooks_free(void* pointer);
 void* hooks_memalign(size_t alignment, size_t bytes);
 void* hooks_aligned_alloc(size_t alignment, size_t bytes);
@@ -174,6 +176,10 @@
   return g_dispatch->mallopt(param, value);
 }
 
+int hooks_malloc_info(int options, FILE* fp) {
+  return g_dispatch->malloc_info(options, fp);
+}
+
 void* hooks_aligned_alloc(size_t alignment, size_t size) {
   if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
     if (!powerof2(alignment) || (size % alignment) != 0) {
@@ -191,7 +197,7 @@
 
 int hooks_posix_memalign(void** memptr, size_t alignment, size_t size) {
   if (__memalign_hook != nullptr && __memalign_hook != default_memalign_hook) {
-    if (!powerof2(alignment)) {
+    if (alignment < sizeof(void*) || !powerof2(alignment)) {
       return EINVAL;
     }
     *memptr = __memalign_hook(alignment, size, __builtin_return_address(0));
diff --git a/libc/private/MallocXmlElem.h b/libc/private/MallocXmlElem.h
new file mode 100644
index 0000000..04d3eee
--- /dev/null
+++ b/libc/private/MallocXmlElem.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <private/bionic_macros.h>
+
+class MallocXmlElem {
+ public:
+  // Name must be valid throughout lifetime of the object.
+  explicit MallocXmlElem(FILE* fp, const char* name,
+                         const char* attr_fmt = nullptr, ...) : fp_(fp), name_(name) {
+    fprintf(fp, "<%s", name_);
+    if (attr_fmt != nullptr) {
+      va_list args;
+      va_start(args, attr_fmt);
+      fputc(' ', fp_);
+      vfprintf(fp_, attr_fmt, args);
+      va_end(args);
+    }
+    fputc('>', fp_);
+  }
+
+  ~MallocXmlElem() noexcept {
+    fprintf(fp_, "</%s>", name_);
+  }
+
+  void Contents(const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    vfprintf(fp_, fmt, args);
+    va_end(args);
+  }
+
+private:
+  FILE* fp_;
+  const char* name_;
+
+  BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(MallocXmlElem);
+};
diff --git a/libc/private/bionic_malloc_dispatch.h b/libc/private/bionic_malloc_dispatch.h
index 0dce03d..aea3a1c 100644
--- a/libc/private/bionic_malloc_dispatch.h
+++ b/libc/private/bionic_malloc_dispatch.h
@@ -31,6 +31,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <stdio.h>
 #include <private/bionic_config.h>
 
 // Entry in malloc dispatch table.
@@ -38,6 +39,7 @@
 typedef void (*MallocFree)(void*);
 typedef struct mallinfo (*MallocMallinfo)();
 typedef void* (*MallocMalloc)(size_t);
+typedef int (*MallocMallocInfo)(int, FILE*);
 typedef size_t (*MallocMallocUsableSize)(const void*);
 typedef void* (*MallocMemalign)(size_t, size_t);
 typedef int (*MallocPosixMemalign)(void**, size_t, size_t);
@@ -73,6 +75,7 @@
   MallocMallocEnable malloc_enable;
   MallocMallopt mallopt;
   MallocAlignedAlloc aligned_alloc;
+  MallocMallocInfo malloc_info;
 } __attribute__((aligned(32)));
 
 #endif