Merge "Give init visibility into bionic headers."
diff --git a/libc/Android.bp b/libc/Android.bp
index b587a51..d985c81 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -95,7 +95,6 @@
 
     header_libs: [
         "libc_headers",
-        "gwp_asan_headers",
         "liblog_headers",  // needed by bionic/libc/async_safe/include
     ],
     export_header_lib_headers: [
@@ -2324,6 +2323,16 @@
     first_version: "9",
     // APIs implemented in asm don't have debug info: http://b/190554910.
     allow_untyped_symbols: true,
+    export_header_libs: [
+        "common_libc",
+        "libc_uapi",
+        "libc_kernel_android_uapi_linux",
+        "libc_kernel_android_scsi",
+        "libc_asm_arm",
+        "libc_asm_arm64",
+        "libc_asm_x86",
+        "libc_asm_x86_64",
+    ],
 }
 
 ndk_library {
diff --git a/libc/async_safe/Android.bp b/libc/async_safe/Android.bp
index 531317d..eb690dd 100644
--- a/libc/async_safe/Android.bp
+++ b/libc/async_safe/Android.bp
@@ -30,13 +30,8 @@
     stl: "none",
 
     apex_available: [
+        "//apex_available:anyapex",
         "//apex_available:platform",
-        "com.android.runtime",
-        "com.android.art",
-        "com.android.art.debug",
-        "com.android.media",
-        "com.android.media.swcodec",
-        "com.android.virt",
     ],
     min_sdk_version: "apex_inherit",
 }
diff --git a/libc/bionic/malloc_common.cpp b/libc/bionic/malloc_common.cpp
index 9744968..e159fdc 100644
--- a/libc/bionic/malloc_common.cpp
+++ b/libc/bionic/malloc_common.cpp
@@ -333,6 +333,14 @@
 
     return EnableGwpAsan(*reinterpret_cast<android_mallopt_gwp_asan_options_t*>(arg));
   }
+  if (opcode == M_MEMTAG_STACK_IS_ON) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
+    return true;
+  }
   errno = ENOTSUP;
   return false;
 }
diff --git a/libc/bionic/malloc_common_dynamic.cpp b/libc/bionic/malloc_common_dynamic.cpp
index 6c2f4d9..97e8d15 100644
--- a/libc/bionic/malloc_common_dynamic.cpp
+++ b/libc/bionic/malloc_common_dynamic.cpp
@@ -533,6 +533,14 @@
 
     return EnableGwpAsan(*reinterpret_cast<android_mallopt_gwp_asan_options_t*>(arg));
   }
+  if (opcode == M_MEMTAG_STACK_IS_ON) {
+    if (arg == nullptr || arg_size != sizeof(bool)) {
+      errno = EINVAL;
+      return false;
+    }
+    *reinterpret_cast<bool*>(arg) = atomic_load(&__libc_globals->memtag_stack);
+    return true;
+  }
   // Try heapprofd's mallopt, as it handles options not covered here.
   return HeapprofdMallopt(opcode, arg, arg_size);
 }
diff --git a/libc/malloc_debug/RecordData.cpp b/libc/malloc_debug/RecordData.cpp
index 5c550c0..a829a09 100644
--- a/libc/malloc_debug/RecordData.cpp
+++ b/libc/malloc_debug/RecordData.cpp
@@ -48,44 +48,44 @@
 RecordEntry::RecordEntry() : tid_(gettid()) {
 }
 
-std::string ThreadCompleteEntry::GetString() const {
-  return android::base::StringPrintf("%d: thread_done 0x0\n", tid_);
+bool ThreadCompleteEntry::Write(int fd) const {
+  return dprintf(fd, "%d: thread_done 0x0\n", tid_) > 0;
 }
 
 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_);
+bool MallocEntry::Write(int fd) const {
+  return dprintf(fd, "%d: malloc %p %zu\n", tid_, pointer_, size_) > 0;
 }
 
 FreeEntry::FreeEntry(void* pointer) : AllocEntry(pointer) {}
 
-std::string FreeEntry::GetString() const {
-  return android::base::StringPrintf("%d: free %p\n", tid_, pointer_);
+bool FreeEntry::Write(int fd) const {
+  return dprintf(fd, "%d: free %p\n", tid_, pointer_) > 0;
 }
 
 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_);
+bool CallocEntry::Write(int fd) const {
+  return dprintf(fd, "%d: calloc %p %zu %zu\n", tid_, pointer_, nmemb_, size_) > 0;
 }
 
 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_);
+bool ReallocEntry::Write(int fd) const {
+  return dprintf(fd, "%d: realloc %p %p %zu\n", tid_, pointer_, old_pointer_, size_) > 0;
 }
 
 // aligned_alloc, posix_memalign, memalign, 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_);
+bool MemalignEntry::Write(int fd) const {
+  return dprintf(fd, "%d: memalign %p %zu %zu\n", tid_, pointer_, alignment_, size_) > 0;
 }
 
 struct ThreadData {
@@ -112,59 +112,37 @@
   }
 }
 
-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();
+RecordData* RecordData::record_obj_ = nullptr;
+
+void RecordData::WriteData(int, siginfo_t*, void*) {
+  // Dump from here, the function must not allocate so this is safe.
+  record_obj_->WriteEntries();
 }
 
-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.
+void RecordData::WriteEntries() {
+  std::lock_guard<std::mutex> entries_lock(entries_lock_);
+  if (cur_index_ == 0) {
+    info_log("No alloc entries to write.");
     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 {
+  if (dump_fd == -1) {
     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;
+    return;
   }
 
-  dump_ = false;
+  for (size_t i = 0; i < cur_index_; i++) {
+    if (!entries_[i]->Write(dump_fd)) {
+      error_log("Failed to write record alloc information: %s", strerror(errno));
+      break;
+    }
+  }
+  close(dump_fd);
+
+  // Mark the entries dumped.
+  cur_index_ = 0U;
 }
 
 RecordData::RecordData() {
@@ -172,8 +150,9 @@
 }
 
 bool RecordData::Initialize(const Config& config) {
+  record_obj_ = this;
   struct sigaction64 dump_act = {};
-  dump_act.sa_sigaction = RecordDump;
+  dump_act.sa_sigaction = RecordData::WriteData;
   dump_act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK;
   if (sigaction64(config.record_allocs_signal(), &dump_act, nullptr) != 0) {
     error_log("Unable to set up record dump signal function: %s", strerror(errno));
@@ -186,24 +165,27 @@
              config.record_allocs_signal(), getpid());
   }
 
-  num_entries_ = config.record_allocs_num_entries();
-  entries_ = new const RecordEntry*[num_entries_];
-  cur_index_ = 0;
-  dump_ = false;
+  entries_.resize(config.record_allocs_num_entries());
+  cur_index_ = 0U;
   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;
+  std::lock_guard<std::mutex> entries_lock(entries_lock_);
+  if (cur_index_ == entries_.size()) {
+    // Maxed out, throw the entry away.
+    return;
+  }
+
+  entries_[cur_index_++].reset(entry);
+  if (cur_index_ == entries_.size()) {
+    info_log("Maximum number of records added, all new operations will be dropped.");
   }
 }
 
@@ -215,9 +197,4 @@
   }
 
   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
index 3d37529..43dba6a 100644
--- a/libc/malloc_debug/RecordData.h
+++ b/libc/malloc_debug/RecordData.h
@@ -29,12 +29,15 @@
 #pragma once
 
 #include <pthread.h>
+#include <signal.h>
 #include <stdint.h>
 #include <unistd.h>
 
 #include <atomic>
+#include <memory>
 #include <mutex>
 #include <string>
+#include <vector>
 
 #include <platform/bionic/macros.h>
 
@@ -43,7 +46,7 @@
   RecordEntry();
   virtual ~RecordEntry() = default;
 
-  virtual std::string GetString() const = 0;
+  virtual bool Write(int fd) const = 0;
 
  protected:
   pid_t tid_;
@@ -57,7 +60,7 @@
   ThreadCompleteEntry() = default;
   virtual ~ThreadCompleteEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  private:
   BIONIC_DISALLOW_COPY_AND_ASSIGN(ThreadCompleteEntry);
@@ -80,7 +83,7 @@
   MallocEntry(void* pointer, size_t size);
   virtual ~MallocEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  protected:
   size_t size_;
@@ -94,7 +97,7 @@
   explicit FreeEntry(void* pointer);
   virtual ~FreeEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  private:
   BIONIC_DISALLOW_COPY_AND_ASSIGN(FreeEntry);
@@ -105,7 +108,7 @@
   CallocEntry(void* pointer, size_t size, size_t nmemb);
   virtual ~CallocEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  protected:
   size_t nmemb_;
@@ -119,7 +122,7 @@
   ReallocEntry(void* pointer, size_t size, void* old_pointer);
   virtual ~ReallocEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  protected:
   void* old_pointer_;
@@ -134,7 +137,7 @@
   MemalignEntry(void* pointer, size_t size, size_t alignment);
   virtual ~MemalignEntry() = default;
 
-  std::string GetString() const override;
+  bool Write(int fd) const override;
 
  protected:
   size_t alignment_;
@@ -155,19 +158,18 @@
   void AddEntry(const RecordEntry* entry);
   void AddEntryOnly(const RecordEntry* entry);
 
-  void SetToDump() { dump_ = true; }
-
   pthread_key_t key() { return key_; }
 
  private:
-  void Dump();
+  static void WriteData(int, siginfo_t*, void*);
+  static RecordData* record_obj_;
 
-  std::mutex dump_lock_;
+  void WriteEntries();
+
+  std::mutex entries_lock_;
   pthread_key_t key_;
-  const RecordEntry** entries_ = nullptr;
-  size_t num_entries_ = 0;
-  std::atomic_uint cur_index_;
-  std::atomic_bool dump_;
+  std::vector<std::unique_ptr<const RecordEntry>> entries_;
+  size_t cur_index_;
   std::string dump_file_;
 
   BIONIC_DISALLOW_COPY_AND_ASSIGN(RecordData);
diff --git a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
index 961ddd3..c6378f5 100644
--- a/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
+++ b/libc/malloc_debug/tests/malloc_debug_unit_tests.cpp
@@ -81,6 +81,9 @@
 bool debug_write_malloc_leak_info(FILE*);
 void debug_dump_heap(const char*);
 
+void malloc_enable();
+void malloc_disable();
+
 __END_DECLS
 
 // Change the slow threshold since some tests take more than 2 seconds.
@@ -108,16 +111,16 @@
     initialized = false;
     resetLogs();
     backtrace_fake_clear_all();
-    if (!record_filename.empty()) {
-      // Delete the record data file if it exists.
-      unlink(record_filename.c_str());
-    }
   }
 
   void TearDown() override {
     if (initialized) {
       debug_finalize();
     }
+    if (!record_filename.empty()) {
+      // Try to delete the record data file even it doesn't exist.
+      unlink(record_filename.c_str());
+    }
   }
 
   void Init(const char* options) {
@@ -2227,12 +2230,6 @@
 
   // 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;
@@ -2242,8 +2239,6 @@
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   ASSERT_STREQ("", getFakeLogPrint().c_str());
-
-  debug_free(pointer);
 }
 
 TEST_F(MallocDebugTest, record_allocs_no_header) {
@@ -2282,11 +2277,6 @@
 
   // 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;
@@ -2295,9 +2285,9 @@
   ASSERT_STREQ(expected.c_str(), actual.c_str());
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
-  ASSERT_STREQ("", getFakeLogPrint().c_str());
-
-  debug_free(pointer);
+  ASSERT_STREQ(
+      "4 malloc_debug Maximum number of records added, all new operations will be dropped.\n",
+      getFakeLogPrint().c_str());
 }
 
 TEST_F(MallocDebugTest, record_allocs_thread_done) {
@@ -2319,12 +2309,6 @@
 
   // 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;
@@ -2334,14 +2318,12 @@
 
   ASSERT_STREQ("", getFakeLogBuf().c_str());
   ASSERT_STREQ("", getFakeLogPrint().c_str());
-
-  debug_free(pointer);
 }
 
 TEST_F(MallocDebugTest, record_allocs_file_name_fail) {
   InitRecordAllocs("record_allocs=5");
 
-  // Delete the special.txt file and create a symbolic link there to
+  // Delete the records file and create a symbolic link there to
   // make sure the create file will fail.
   unlink(record_filename.c_str());
 
@@ -2357,12 +2339,6 @@
 
   // 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;
@@ -2373,11 +2349,6 @@
 
   // 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_filename, &actual));
   ASSERT_STREQ(expected.c_str(), actual.c_str());
@@ -2389,6 +2360,41 @@
   ASSERT_STREQ(expected_log.c_str(), getFakeLogPrint().c_str());
 }
 
+TEST_F(MallocDebugTest, record_allocs_no_entries_to_write) {
+  InitRecordAllocs("record_allocs=5");
+
+  kill(getpid(), SIGRTMAX - 18);
+
+  std::string actual;
+  ASSERT_FALSE(android::base::ReadFileToString(record_filename, &actual));
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("4 malloc_debug No alloc entries to write.\n", getFakeLogPrint().c_str());
+}
+
+TEST_F(MallocDebugTest, record_allocs_write_entries_does_not_allocate) {
+  InitRecordAllocs("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);
+
+  malloc_disable();
+  kill(getpid(), SIGRTMAX - 18);
+  malloc_enable();
+
+  std::string actual;
+  ASSERT_TRUE(android::base::ReadFileToString(record_filename, &actual));
+  ASSERT_STREQ(expected.c_str(), actual.c_str());
+
+  ASSERT_STREQ("", getFakeLogBuf().c_str());
+  ASSERT_STREQ("", getFakeLogPrint().c_str());
+}
+
 TEST_F(MallocDebugTest, verify_pointers) {
   Init("verify_pointers");
 
diff --git a/libc/platform/bionic/malloc.h b/libc/platform/bionic/malloc.h
index b0c7071..ecc8743 100644
--- a/libc/platform/bionic/malloc.h
+++ b/libc/platform/bionic/malloc.h
@@ -100,6 +100,9 @@
   //   arg_size = sizeof(android_mallopt_gwp_asan_options_t)
   M_INITIALIZE_GWP_ASAN = 10,
 #define M_INITIALIZE_GWP_ASAN M_INITIALIZE_GWP_ASAN
+  // Query whether memtag stack is enabled for this process.
+  M_MEMTAG_STACK_IS_ON = 11,
+#define M_MEMTAG_STACK_IS_ON M_MEMTAG_STACK_IS_ON
 };
 
 typedef struct {
diff --git a/tests/NOTICE b/tests/NOTICE
index 8c3483c..167f90b 100644
--- a/tests/NOTICE
+++ b/tests/NOTICE
@@ -383,6 +383,22 @@
 -------------------------------------------------------------------
 
 Copyright (C) 2022 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.
+
+-------------------------------------------------------------------
+
+Copyright (C) 2022 The Android Open Source Project
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/tests/libs/Android.bp b/tests/libs/Android.bp
index 5edf97c..66a902e 100644
--- a/tests/libs/Android.bp
+++ b/tests/libs/Android.bp
@@ -1663,6 +1663,7 @@
        memtag_heap: true,
      },
    },
+   header_libs: ["bionic_libc_platform_headers"],
 }
 
 cc_test {
@@ -1677,6 +1678,7 @@
        memtag_heap: true,
      },
    },
+   header_libs: ["bionic_libc_platform_headers"],
 }
 
 cc_genrule {
diff --git a/tests/libs/stack_tagging_helper.cpp b/tests/libs/stack_tagging_helper.cpp
index f6739b6..a239dc1 100644
--- a/tests/libs/stack_tagging_helper.cpp
+++ b/tests/libs/stack_tagging_helper.cpp
@@ -26,6 +26,8 @@
 #include <unistd.h>
 #include <thread>
 
+#include <bionic/malloc.h>
+
 #include "libs_utils.h"
 
 #if defined(__aarch64__)
@@ -252,6 +254,12 @@
   t.join();
 }
 
+void test_android_mallopt() {
+  bool memtag_stack;
+  CHECK(android_mallopt(M_MEMTAG_STACK_IS_ON, &memtag_stack, sizeof(memtag_stack)));
+  CHECK(memtag_stack);
+}
+
 int main(int argc, char** argv) {
   if (argc < 2) {
     printf("nothing to do\n");
@@ -283,6 +291,11 @@
     return 0;
   }
 
+  if (strcmp(argv[1], "android_mallopt") == 0) {
+    test_android_mallopt();
+    return 0;
+  }
+
   printf("unrecognized command: %s\n", argv[1]);
   return 1;
 }
diff --git a/tests/malloc_test.cpp b/tests/malloc_test.cpp
index 69f8506..3cc5bbd 100644
--- a/tests/malloc_test.cpp
+++ b/tests/malloc_test.cpp
@@ -1383,6 +1383,15 @@
 #endif
 }
 
+TEST(android_mallopt, memtag_stack_is_on) {
+#if defined(__BIONIC__)
+  bool memtag_stack;
+  EXPECT_TRUE(android_mallopt(M_MEMTAG_STACK_IS_ON, &memtag_stack, sizeof(memtag_stack)));
+#else
+  GTEST_SKIP() << "bionic extension";
+#endif
+}
+
 void TestHeapZeroing(int num_iterations, int (*get_alloc_size)(int iteration)) {
   std::vector<void*> allocs;
   constexpr int kMaxBytesToCheckZero = 64;
diff --git a/tests/memtag_stack_test.cpp b/tests/memtag_stack_test.cpp
index 9ff8899..1b336bc 100644
--- a/tests/memtag_stack_test.cpp
+++ b/tests/memtag_stack_test.cpp
@@ -47,7 +47,7 @@
 INSTANTIATE_TEST_SUITE_P(, MemtagStackTest,
                          testing::Combine(testing::Values("vfork_execve", "vfork_execl",
                                                           "vfork_exit", "longjmp",
-                                                          "longjmp_sigaltstack"),
+                                                          "longjmp_sigaltstack", "android_mallopt"),
                                           testing::Bool()),
                          [](const ::testing::TestParamInfo<MemtagStackTest::ParamType>& info) {
                            std::string s = std::get<0>(info.param);