Merge "Fix allocations escaping malloc debug."
diff --git a/libc/bionic/jemalloc_wrapper.cpp b/libc/bionic/jemalloc_wrapper.cpp
index 7d04457..ef488ee 100644
--- a/libc/bionic/jemalloc_wrapper.cpp
+++ b/libc/bionic/jemalloc_wrapper.cpp
@@ -140,30 +140,32 @@
     return -1;
   }
 
-  MallocXmlElem root(fp, "malloc", "version=\"jemalloc-1\"");
+  fflush(fp);
+  int fd = fileno(fp);
+  MallocXmlElem root(fd, "malloc", "version=\"jemalloc-1\"");
 
   // Dump all of the large allocations in the arenas.
   for (size_t i = 0; i < je_mallinfo_narenas(); i++) {
     struct mallinfo mi = je_mallinfo_arena_info(i);
     if (mi.hblkhd != 0) {
-      MallocXmlElem arena_elem(fp, "heap", "nr=\"%d\"", i);
+      MallocXmlElem arena_elem(fd, "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);
+        MallocXmlElem(fd, "allocated-large").Contents("%zu", mi.ordblks);
+        MallocXmlElem(fd, "allocated-huge").Contents("%zu", mi.uordblks);
+        MallocXmlElem(fd, "allocated-bins").Contents("%zu", mi.fsmblks);
 
         size_t total = 0;
         for (size_t j = 0; j < je_mallinfo_nbins(); j++) {
           struct mallinfo mi = je_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);
+            MallocXmlElem bin_elem(fd, "bin", "nr=\"%d\"", j);
+            MallocXmlElem(fd, "allocated").Contents("%zu", mi.ordblks);
+            MallocXmlElem(fd, "nmalloc").Contents("%zu", mi.uordblks);
+            MallocXmlElem(fd, "ndalloc").Contents("%zu", mi.fordblks);
             total += mi.ordblks;
           }
         }
-        MallocXmlElem(fp, "bins-total").Contents("%zu", total);
+        MallocXmlElem(fd, "bins-total").Contents("%zu", total);
       }
     }
   }
diff --git a/libc/malloc_debug/PointerData.cpp b/libc/malloc_debug/PointerData.cpp
index ec7e42d..b1e28b7 100644
--- a/libc/malloc_debug/PointerData.cpp
+++ b/libc/malloc_debug/PointerData.cpp
@@ -554,7 +554,7 @@
   return pointers_.count(pointer) != 0;
 }
 
-void PointerData::DumpLiveToFile(FILE* fp) {
+void PointerData::DumpLiveToFile(int fd) {
   std::vector<ListInfoType> list;
 
   std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
@@ -566,13 +566,13 @@
     total_memory += info.size * info.num_allocations;
   }
 
-  fprintf(fp, "Total memory: %zu\n", total_memory);
-  fprintf(fp, "Allocation records: %zd\n", list.size());
-  fprintf(fp, "Backtrace size: %zu\n", g_debug->config().backtrace_frames());
-  fprintf(fp, "\n");
+  dprintf(fd, "Total memory: %zu\n", total_memory);
+  dprintf(fd, "Allocation records: %zd\n", list.size());
+  dprintf(fd, "Backtrace size: %zu\n", g_debug->config().backtrace_frames());
+  dprintf(fd, "\n");
 
   for (const auto& info : list) {
-    fprintf(fp, "z %d  sz %8zu  num    %zu  bt", (info.zygote_child_alloc) ? 1 : 0, info.size,
+    dprintf(fd, "z %d  sz %8zu  num    %zu  bt", (info.zygote_child_alloc) ? 1 : 0, info.size,
             info.num_allocations);
     FrameInfoType* frame_info = info.frame_info;
     if (frame_info != nullptr) {
@@ -580,22 +580,22 @@
         if (frame_info->frames[i] == 0) {
           break;
         }
-        fprintf(fp, " %" PRIxPTR, frame_info->frames[i]);
+        dprintf(fd, " %" PRIxPTR, frame_info->frames[i]);
       }
     }
-    fprintf(fp, "\n");
+    dprintf(fd, "\n");
     if (info.backtrace_info != nullptr) {
-      fprintf(fp, "  bt_info");
+      dprintf(fd, "  bt_info");
       for (const auto& frame : *info.backtrace_info) {
-        fprintf(fp, " {");
+        dprintf(fd, " {");
         if (frame.map_info != nullptr && !frame.map_info->name.empty()) {
-          fprintf(fp, "\"%s\"", frame.map_info->name.c_str());
+          dprintf(fd, "\"%s\"", frame.map_info->name.c_str());
         } else {
-          fprintf(fp, "\"\"");
+          dprintf(fd, "\"\"");
         }
-        fprintf(fp, " %" PRIx64, frame.rel_pc);
+        dprintf(fd, " %" PRIx64, frame.rel_pc);
         if (frame.function_name.empty()) {
-          fprintf(fp, " \"\" 0}");
+          dprintf(fd, " \"\" 0}");
         } else {
           char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr,
                                                 nullptr);
@@ -605,11 +605,11 @@
           } else {
             name = frame.function_name.c_str();
           }
-          fprintf(fp, " \"%s\" %" PRIx64 "}", name, frame.function_offset);
+          dprintf(fd, " \"%s\" %" PRIx64 "}", name, frame.function_offset);
           free(demangled_name);
         }
       }
-      fprintf(fp, "\n");
+      dprintf(fd, "\n");
     }
   }
 }
diff --git a/libc/malloc_debug/PointerData.h b/libc/malloc_debug/PointerData.h
index c7958f3..78f0ed8 100644
--- a/libc/malloc_debug/PointerData.h
+++ b/libc/malloc_debug/PointerData.h
@@ -152,7 +152,7 @@
 
   static void GetAllocList(std::vector<ListInfoType>* list);
   static void LogLeaks();
-  static void DumpLiveToFile(FILE* fp);
+  static void DumpLiveToFile(int fd);
 
   static void GetInfo(uint8_t** info, size_t* overall_size, size_t* info_size, size_t* total_memory,
                       size_t* backtrace_size);
diff --git a/libc/malloc_debug/malloc_debug.cpp b/libc/malloc_debug/malloc_debug.cpp
index c030d54..3c0e630 100644
--- a/libc/malloc_debug/malloc_debug.cpp
+++ b/libc/malloc_debug/malloc_debug.cpp
@@ -785,16 +785,23 @@
   if (DebugCallsDisabled() || !g_debug->TrackPointers()) {
     return g_dispatch->malloc_info(options, fp);
   }
+
+  // Make sure any pending output is written to the file.
+  fflush(fp);
+
   ScopedConcurrentLock lock;
   ScopedDisableDebugCalls disable;
 
-  MallocXmlElem root(fp, "malloc", "version=\"debug-malloc-1\"");
+  // Avoid any issues where allocations are made that will be freed
+  // in the fclose.
+  int fd = fileno(fp);
+  MallocXmlElem root(fd, "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);
+    MallocXmlElem alloc(fd, "allocation", "nr=\"%zu\"", alloc_num);
 
     size_t total = 1;
     size_t size = list[i].size;
@@ -802,8 +809,8 @@
       i++;
       total++;
     }
-    MallocXmlElem(fp, "size").Contents("%zu", list[i].size);
-    MallocXmlElem(fp, "total").Contents("%zu", total);
+    MallocXmlElem(fd, "size").Contents("%zu", list[i].size);
+    MallocXmlElem(fd, "total").Contents("%zu", total);
     alloc_num++;
   }
   return 0;
@@ -905,25 +912,28 @@
 
 static std::mutex g_dump_lock;
 
-static void write_dump(FILE* fp) {
-  fprintf(fp, "Android Native Heap Dump v1.2\n\n");
+static void write_dump(int fd) {
+  dprintf(fd, "Android Native Heap Dump v1.2\n\n");
 
   std::string fingerprint = android::base::GetProperty("ro.build.fingerprint", "unknown");
-  fprintf(fp, "Build fingerprint: '%s'\n\n", fingerprint.c_str());
+  dprintf(fd, "Build fingerprint: '%s'\n\n", fingerprint.c_str());
 
-  PointerData::DumpLiveToFile(fp);
+  PointerData::DumpLiveToFile(fd);
 
-  fprintf(fp, "MAPS\n");
+  dprintf(fd, "MAPS\n");
   std::string content;
   if (!android::base::ReadFileToString("/proc/self/maps", &content)) {
-    fprintf(fp, "Could not open /proc/self/maps\n");
+    dprintf(fd, "Could not open /proc/self/maps\n");
   } else {
-    fprintf(fp, "%s", content.c_str());
+    dprintf(fd, "%s", content.c_str());
   }
-  fprintf(fp, "END\n");
+  dprintf(fd, "END\n");
 }
 
 bool debug_write_malloc_leak_info(FILE* fp) {
+  // Make sure any pending output is written to the file.
+  fflush(fp);
+
   ScopedConcurrentLock lock;
   ScopedDisableDebugCalls disable;
 
@@ -933,7 +943,8 @@
     return false;
   }
 
-  write_dump(fp);
+  write_dump(fileno(fp));
+
   return true;
 }
 
@@ -943,13 +954,13 @@
 
   std::lock_guard<std::mutex> guard(g_dump_lock);
 
-  FILE* fp = fopen(file_name, "w+e");
-  if (fp == nullptr) {
+  int fd = open(file_name, O_RDWR | O_CREAT | O_NOFOLLOW | O_TRUNC | O_CLOEXEC, 0644);
+  if (fd == -1) {
     error_log("Unable to create file: %s", file_name);
     return;
   }
 
   error_log("Dumping to file: %s\n", file_name);
-  write_dump(fp);
-  fclose(fp);
+  write_dump(fd);
+  close(fd);
 }
diff --git a/libc/malloc_debug/tests/malloc_debug_system_tests.cpp b/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
index 0716758..67bb8d9 100644
--- a/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_system_tests.cpp
@@ -37,6 +37,7 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <gtest/gtest.h>
 #include <log/log.h>
@@ -174,6 +175,7 @@
 }
 
 static void FindStrings(pid_t pid, std::vector<const char*> match_strings,
+                        std::vector<const char*> no_match_strings = std::vector<const char*>{},
                         time_t timeout_seconds = kTimeoutSeconds) {
   std::string log_str;
   time_t start = time(nullptr);
@@ -181,12 +183,18 @@
   while (true) {
     GetLogStr(pid, &log_str);
     found_all = true;
+    // Look for the expected strings.
     for (auto str : match_strings) {
       if (log_str.find(str) == std::string::npos) {
         found_all = false;
         break;
       }
     }
+
+    // Verify the unexpected strings are not present.
+    for (auto str : no_match_strings) {
+      ASSERT_TRUE(log_str.find(str) == std::string::npos) << "Unexpectedly found '" << str << "' in log output:\n" << log_str;
+    }
     if (found_all) {
       return;
     }
@@ -194,7 +202,7 @@
       break;
     }
   }
-  ASSERT_TRUE(found_all) << "Didn't find expected log output:\n" + log_str;
+  ASSERT_TRUE(found_all) << "Didn't find expected log output:\n" << log_str;
 }
 
 TEST(MallocTests, DISABLED_smoke) {}
@@ -464,3 +472,45 @@
         << "Found crash in log.\nLog message: " << log_str;
   }
 }
+
+TEST(MallocTests, DISABLED_write_leak_info) {
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+
+  FILE* fp = fdopen(tf.fd, "w+");
+  if (fp == nullptr) {
+    printf("Unable to create %s\n", tf.path);
+    _exit(1);
+  }
+  tf.release();
+
+  void* ptr = malloc(1000);
+  if (ptr == nullptr) {
+    printf("malloc failed\n");
+    _exit(1);
+  }
+  memset(ptr, 0, 1000);
+
+  android_mallopt(M_WRITE_MALLOC_LEAK_INFO_TO_FILE, fp, sizeof(fp));
+
+  fclose(fp);
+
+  free(ptr);
+}
+
+TEST(MallocDebugSystemTest, write_leak_info_no_header) {
+  pid_t pid;
+  ASSERT_NO_FATAL_FAILURE(Exec("MallocTests.DISABLED_write_leak_info", "verbose backtrace", &pid, 0));
+
+  ASSERT_NO_FATAL_FAILURE(FindStrings(pid, std::vector<const char*>{"malloc debug enabled"},
+
+                          std::vector<const char*>{" HAS INVALID TAG ", "USED AFTER FREE ", "UNKNOWN POINTER "}));
+}
+
+TEST(MallocDebugSystemTest, write_leak_info_header) {
+  pid_t pid;
+  ASSERT_NO_FATAL_FAILURE(Exec("MallocTests.DISABLED_write_leak_info", "verbose backtrace guard", &pid, 0));
+
+  ASSERT_NO_FATAL_FAILURE(FindStrings(pid, std::vector<const char*>{"malloc debug enabled"},
+                          std::vector<const char*>{" HAS INVALID TAG ", "USED AFTER FREE ", "UNKNOWN POINTER "}));
+}
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 0238d10..70457b9 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -27,6 +27,8 @@
 
 #include <algorithm>
 #include <memory>
+#include <string>
+#include <string_view>
 #include <thread>
 #include <vector>
 #include <utility>
@@ -73,6 +75,9 @@
 void* debug_valloc(size_t);
 #endif
 
+bool debug_write_malloc_leak_info(FILE*);
+void debug_dump_heap(const char*);
+
 __END_DECLS
 
 constexpr char DIVIDER[] =
@@ -1302,6 +1307,10 @@
 }
 
 static std::string SanitizeHeapData(const std::string& data) {
+  if (data.empty()) {
+    return data;
+  }
+
   // Remove the map data since it's not consistent.
   std::string sanitized;
   bool skip_map_data = false;
@@ -1329,7 +1338,7 @@
       sanitized += line + '\n';
     }
   }
-  return sanitized;
+  return android::base::Trim(sanitized);
 }
 
 void MallocDebugTest::BacktraceDumpOnSignal(bool trigger_with_alloc) {
@@ -1402,9 +1411,7 @@
 z 1  sz       40  num    1  bt 300 400
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1463,9 +1470,7 @@
 z 0  sz      300  num    1  bt 100 200
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1513,9 +1518,7 @@
 z 0  sz      300  num    2  bt 100 200
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -1575,9 +1578,7 @@
   bt_info {"" 100 "fake1" a} {"" 200 "fake2" 14}
 MAPS
 MAP_DATA
-END
-
-)";
+END)";
   ASSERT_STREQ(expected.c_str(), sanitized.c_str()) << "Actual data: \n" << actual;
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
@@ -2472,15 +2473,19 @@
 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));
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, fp));
+  ASSERT_EQ(0, fclose(fp));
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
   auto root = doc.FirstChildElement();
   ASSERT_TRUE(root != nullptr);
   ASSERT_STREQ("malloc", root->Name());
@@ -2501,17 +2506,21 @@
   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));
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
+  ASSERT_EQ(0, debug_malloc_info(0, fp));
+  ASSERT_EQ(0, fclose(fp));
 
-  SCOPED_TRACE(testing::Message() << "Output:\n" << buffer);
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+
+  SCOPED_TRACE(testing::Message() << "Output:\n" << contents);
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buffer));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
   auto root = doc.FirstChildElement();
   ASSERT_TRUE(root != nullptr);
   ASSERT_STREQ("malloc", root->Name());
@@ -2548,3 +2557,134 @@
   ASSERT_EQ(tinyxml2::XML_SUCCESS, alloc->FirstChildElement("total")->QueryIntText(&val));
   ASSERT_EQ(1, val);
 }
+
+static void AllocPtrsWithBacktrace(std::vector<void*>* ptrs) {
+  backtrace_fake_add(std::vector<uintptr_t> {0xf, 0xe, 0xd, 0xc});
+  void* ptr = debug_malloc(1024);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 1024);
+  ptrs->push_back(ptr);
+
+  backtrace_fake_add(std::vector<uintptr_t> {0xbc000, 0xbc001, 0xbc002});
+  ptr = debug_malloc(500);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 500);
+  ptrs->push_back(ptr);
+
+  backtrace_fake_add(std::vector<uintptr_t> {0x104});
+  ptr = debug_malloc(100);
+  ASSERT_TRUE(ptr != nullptr);
+  memset(ptr, 0, 100);
+  ptrs->push_back(ptr);
+}
+
+static constexpr std::string_view kDumpInfo = R"(Android Native Heap Dump v1.2
+
+Build fingerprint: ''
+
+Total memory: 1624
+Allocation records: 3
+Backtrace size: 16
+
+z 0  sz     1024  num    1  bt f e d c
+z 0  sz      500  num    1  bt bc000 bc001 bc002
+z 0  sz      100  num    1  bt 104
+MAPS
+MAP_DATA
+END)";
+
+TEST_F(MallocDebugTest, debug_write_malloc_leak_info) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  FILE* fp = fopen(tf.path, "w+");
+  ASSERT_TRUE(fp != nullptr);
+
+  ASSERT_TRUE(debug_write_malloc_leak_info(fp));
+
+  fclose(fp);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected(kDumpInfo);
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugTest, debug_write_malloc_leak_info_extra_data) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  FILE* fp = fopen(tf.path, "w+");
+  ASSERT_TRUE(fp != nullptr);
+
+  fprintf(fp, "This message should appear before the output.\n");
+  ASSERT_TRUE(debug_write_malloc_leak_info(fp));
+  fprintf(fp, "This message should appear after the output.\n");
+
+  fclose(fp);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected = "This message should appear before the output.\n"
+                         + std::string(kDumpInfo)
+                         + "\nThis message should appear after the output.";
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugTest, dump_heap) {
+  Init("backtrace=16");
+
+  std::vector<void*> ptrs;
+  AllocPtrsWithBacktrace(&ptrs);
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  close(tf.fd);
+  tf.release();
+  debug_dump_heap(tf.path);
+
+  for (auto ptr : ptrs) {
+    debug_free(ptr);
+  }
+  ptrs.clear();
+
+  std::string expected(kDumpInfo);
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
+  contents = SanitizeHeapData(contents);
+  ASSERT_EQ(expected, contents);
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  std::string expected_log = std::string("6 malloc_debug Dumping to file: ") + tf.path + "\n\n";
+  ASSERT_EQ(expected_log, getFakeLogPrint());
+}
diff --git a/libc/private/MallocXmlElem.h b/libc/private/MallocXmlElem.h
index 04d3eee..a367972 100644
--- a/libc/private/MallocXmlElem.h
+++ b/libc/private/MallocXmlElem.h
@@ -18,38 +18,39 @@
 
 #include <stdarg.h>
 #include <stdio.h>
+#include <unistd.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_);
+  explicit MallocXmlElem(int fd, const char* name,
+                         const char* attr_fmt = nullptr, ...) : fd_(fd), name_(name) {
+    dprintf(fd_, "<%s", name_);
     if (attr_fmt != nullptr) {
       va_list args;
       va_start(args, attr_fmt);
-      fputc(' ', fp_);
-      vfprintf(fp_, attr_fmt, args);
+      write(fd_, " ", 1);
+      vdprintf(fd_, attr_fmt, args);
       va_end(args);
     }
-    fputc('>', fp_);
+    write(fd_, ">", 1);
   }
 
   ~MallocXmlElem() noexcept {
-    fprintf(fp_, "</%s>", name_);
+    dprintf(fd_, "</%s>", name_);
   }
 
   void Contents(const char* fmt, ...) {
     va_list args;
     va_start(args, fmt);
-    vfprintf(fp_, fmt, args);
+    vdprintf(fd_, fmt, args);
     va_end(args);
   }
 
 private:
-  FILE* fp_;
+  int fd_;
   const char* name_;
 
   BIONIC_DISALLOW_IMPLICIT_CONSTRUCTORS(MallocXmlElem);
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index 0407553..ebbd247 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -358,15 +358,20 @@
 TEST(malloc, malloc_info) {
 #ifdef __BIONIC__
   SKIP_WITH_HWASAN; // hwasan does not implement malloc_info
-  char* buf;
-  size_t bufsize;
-  FILE* memstream = open_memstream(&buf, &bufsize);
-  ASSERT_NE(nullptr, memstream);
-  ASSERT_EQ(0, malloc_info(0, memstream));
-  ASSERT_EQ(0, fclose(memstream));
+
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
+  ASSERT_EQ(0, malloc_info(0, fp));
+  ASSERT_EQ(0, fclose(fp));
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buf));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
 
   auto root = doc.FirstChildElement();
   ASSERT_NE(nullptr, root);
@@ -416,17 +421,21 @@
 #ifdef __BIONIC__
   SKIP_WITH_HWASAN; // hwasan does not implement malloc_info
 
-  char* buf;
-  size_t bufsize;
-  FILE* memstream = open_memstream(&buf, &bufsize);
-  ASSERT_NE(nullptr, memstream);
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+  FILE* fp = fdopen(tf.fd, "w+");
+  tf.release();
+  ASSERT_TRUE(fp != nullptr);
   size_t mallinfo_before_allocated_bytes = mallinfo().uordblks;
-  ASSERT_EQ(0, malloc_info(0, memstream));
+  ASSERT_EQ(0, malloc_info(0, fp));
   size_t mallinfo_after_allocated_bytes = mallinfo().uordblks;
-  ASSERT_EQ(0, fclose(memstream));
+  ASSERT_EQ(0, fclose(fp));
+
+  std::string contents;
+  ASSERT_TRUE(android::base::ReadFileToString(tf.path, &contents));
 
   tinyxml2::XMLDocument doc;
-  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(buf));
+  ASSERT_EQ(tinyxml2::XML_SUCCESS, doc.Parse(contents.c_str()));
 
   size_t total_allocated_bytes = 0;
   auto root = doc.FirstChildElement();