Rewrite __cxa_atexit / __cxa_finalize
Simplify:
- Use a single memory-mapped region to hold the table of destructors.
Double its capacity each time it is expanded.
- Add a recompaction pass at the end of __cxa_finalize that shifts
entries forward and uses madvise to clean pages.
Bug: http://b/148055738
Test: bionic-unit-tests
Change-Id: Ieb9da2b88640a8a5277d217b43826b5b7e246781
diff --git a/libc/Android.bp b/libc/Android.bp
index 1f54a0a..3448094 100644
--- a/libc/Android.bp
+++ b/libc/Android.bp
@@ -1219,7 +1219,7 @@
// so we can't include them in libc_ndk.a.
"bionic/__cxa_thread_atexit_impl.cpp",
"bionic/android_unsafe_frame_pointer_chase.cpp",
- "stdlib/atexit.c",
+ "bionic/atexit.cpp",
"bionic/fork.cpp",
],
diff --git a/libc/NOTICE b/libc/NOTICE
index 8245ff8..8582054 100644
--- a/libc/NOTICE
+++ b/libc/NOTICE
@@ -1,32 +1,3 @@
- Copyright (c) 2014, ARM Limited
- All rights Reserved.
- Copyright (c) 2014, Linaro Ltd.
-
- 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.
- * Neither the name of the company nor the names of its contributors
- may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
- 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
- HOLDER 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.
-
--------------------------------------------------------------------
-
Copyright (c) 2014, Linaro Limited
All rights reserved.
@@ -5225,34 +5196,6 @@
-------------------------------------------------------------------
-Copyright (c) 2012, Linaro Limited
- 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.
- * Neither the name of the Linaro nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- 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
- HOLDER 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.
-
--------------------------------------------------------------------
-
Copyright (c) 2012-2013, Linaro Limited
All rights reserved.
@@ -5465,33 +5408,6 @@
-------------------------------------------------------------------
-Copyright (c) 2013-2015, Linaro Limited
- 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.
- * Neither the name of the Linaro nor the
- names of its contributors may be used to endorse or promote products
- derived from this software without specific prior written permission.
-
- 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
- HOLDER 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
-
--------------------------------------------------------------------
-
Copyright (c) 2014, Intel Corporation
All rights reserved.
@@ -5632,34 +5548,6 @@
-------------------------------------------------------------------
-Copyright (c) 2017 ARM Ltd
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-2. 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.
-3. The name of the company may not be used to endorse or promote
- products derived from this software without specific prior written
- permission.
-
-THIS SOFTWARE IS PROVIDED BY ARM LTD ``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 ARM LTD 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.
-
--------------------------------------------------------------------
-
Copyright (c)1999 Citrus Project,
All rights reserved.
diff --git a/libc/bionic/atexit.cpp b/libc/bionic/atexit.cpp
new file mode 100644
index 0000000..df306af
--- /dev/null
+++ b/libc/bionic/atexit.cpp
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2020 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 "atexit.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/prctl.h>
+
+#include <async_safe/CHECK.h>
+#include <async_safe/log.h>
+
+#include "platform/bionic/page.h"
+
+extern "C" void __libc_stdio_cleanup();
+extern "C" void __unregister_atfork(void* dso);
+
+namespace {
+
+struct AtexitEntry {
+ void (*fn)(void*); // the __cxa_atexit callback
+ void* arg; // argument for `fn` callback
+ void* dso; // shared module handle
+};
+
+class AtexitArray {
+ public:
+ size_t size() const { return size_; }
+ uint64_t total_appends() const { return total_appends_; }
+ const AtexitEntry& operator[](size_t idx) const { return array_[idx]; }
+
+ bool append_entry(const AtexitEntry& entry);
+ AtexitEntry extract_entry(size_t idx);
+ void recompact();
+
+ private:
+ AtexitEntry* array_;
+ size_t size_;
+ size_t extracted_count_;
+ size_t capacity_;
+
+ // An entry can be appended by a __cxa_finalize callback. Track the number of appends so we
+ // restart concurrent __cxa_finalize passes.
+ uint64_t total_appends_;
+
+ static size_t round_up_to_page_bytes(size_t capacity) {
+ return PAGE_END(capacity * sizeof(AtexitEntry));
+ }
+
+ static size_t next_capacity(size_t capacity) {
+ // Double the capacity each time.
+ size_t result = round_up_to_page_bytes(MAX(1, capacity * 2)) / sizeof(AtexitEntry);
+ CHECK(result > capacity);
+ return result;
+ }
+
+ // Recompact the array if it will save at least one page of memory at the end.
+ bool needs_recompaction() {
+ return round_up_to_page_bytes(size_ - extracted_count_) < round_up_to_page_bytes(size_);
+ }
+
+ void set_writable(bool writable);
+ bool expand_capacity();
+};
+
+} // anonymous namespace
+
+bool AtexitArray::append_entry(const AtexitEntry& entry) {
+ bool result = false;
+
+ set_writable(true);
+ if (size_ < capacity_ || expand_capacity()) {
+ array_[size_++] = entry;
+ ++total_appends_;
+ result = true;
+ }
+ set_writable(false);
+
+ return result;
+}
+
+// Extract an entry and return it.
+AtexitEntry AtexitArray::extract_entry(size_t idx) {
+ AtexitEntry result = array_[idx];
+
+ set_writable(true);
+ array_[idx] = {};
+ ++extracted_count_;
+ set_writable(false);
+
+ return result;
+}
+
+void AtexitArray::recompact() {
+ if (!needs_recompaction()) return;
+
+ set_writable(true);
+
+ // Optimization: quickly skip over the initial non-null entries.
+ size_t src = 0, dst = 0;
+ while (src < size_ && array_[src].fn != nullptr) {
+ ++src;
+ ++dst;
+ }
+
+ // Shift the non-null entries forward, and zero out the removed entries at the end of the array.
+ for (; src < size_; ++src) {
+ const AtexitEntry entry = array_[src];
+ array_[src] = {};
+ if (entry.fn != nullptr) {
+ array_[dst++] = entry;
+ }
+ }
+
+ // If the table uses fewer pages, clean the pages at the end.
+ size_t old_bytes = round_up_to_page_bytes(size_);
+ size_t new_bytes = round_up_to_page_bytes(dst);
+ if (new_bytes < old_bytes) {
+ madvise(reinterpret_cast<char*>(array_) + new_bytes, old_bytes - new_bytes, MADV_DONTNEED);
+ }
+
+ size_ = dst;
+ extracted_count_ = 0;
+
+ set_writable(false);
+}
+
+// Use mprotect to make the array writable or read-only. Returns true on success. Making the array
+// read-only could protect against either unintentional or malicious corruption of the array.
+void AtexitArray::set_writable(bool writable) {
+ if (array_ == nullptr) return;
+ const int prot = PROT_READ | (writable ? PROT_WRITE : 0);
+ if (mprotect(array_, round_up_to_page_bytes(capacity_), prot) != 0) {
+ async_safe_fatal("mprotect failed on atexit array: %s", strerror(errno));
+ }
+}
+
+bool AtexitArray::expand_capacity() {
+ const size_t new_capacity = next_capacity(capacity_);
+ const size_t new_capacity_bytes = round_up_to_page_bytes(new_capacity);
+
+ void* new_pages;
+ if (array_ == nullptr) {
+ new_pages = mmap(nullptr, new_capacity_bytes, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ } else {
+ new_pages =
+ mremap(array_, round_up_to_page_bytes(capacity_), new_capacity_bytes, MREMAP_MAYMOVE);
+ }
+ if (new_pages == MAP_FAILED) {
+ async_safe_format_log(ANDROID_LOG_WARN, "libc",
+ "__cxa_atexit: mmap/mremap failed to allocate %zu bytes: %s",
+ new_capacity_bytes, strerror(errno));
+ return false;
+ }
+
+ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_pages, new_capacity_bytes, "atexit handlers");
+ array_ = static_cast<AtexitEntry*>(new_pages);
+ capacity_ = new_capacity;
+ return true;
+}
+
+static AtexitArray g_array;
+static pthread_mutex_t g_atexit_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static inline void atexit_lock() {
+ pthread_mutex_lock(&g_atexit_lock);
+}
+
+static inline void atexit_unlock() {
+ pthread_mutex_unlock(&g_atexit_lock);
+}
+
+// Register a function to be called either when a library is unloaded (dso != nullptr), or when the
+// program exits (dso == nullptr). The `dso` argument is typically the address of a hidden
+// __dso_handle variable. This function is also used as the backend for the atexit function.
+//
+// See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#dso-dtor.
+//
+int __cxa_atexit(void (*func)(void*), void* arg, void* dso) {
+ int result = -1;
+
+ if (func != nullptr) {
+ atexit_lock();
+ if (g_array.append_entry({.fn = func, .arg = arg, .dso = dso})) {
+ result = 0;
+ }
+ atexit_unlock();
+ }
+
+ return result;
+}
+
+void __cxa_finalize(void* dso) {
+ atexit_lock();
+
+ static uint32_t call_depth = 0;
+ ++call_depth;
+
+restart:
+ const uint64_t total_appends = g_array.total_appends();
+
+ for (ssize_t i = g_array.size() - 1; i >= 0; --i) {
+ if (g_array[i].fn == nullptr || (dso != nullptr && g_array[i].dso != dso)) continue;
+
+ // Clear the entry in the array because its DSO handle will become invalid, and to avoid calling
+ // an entry again if __cxa_finalize is called recursively.
+ const AtexitEntry entry = g_array.extract_entry(i);
+
+ atexit_unlock();
+ entry.fn(entry.arg);
+ atexit_lock();
+
+ if (g_array.total_appends() != total_appends) goto restart;
+ }
+
+ // Avoid recompaction on recursive calls because it's unnecessary and would require earlier,
+ // concurrent __cxa_finalize calls to restart. Skip recompaction on program exit too
+ // (dso == nullptr), because the memory will be reclaimed soon anyway.
+ --call_depth;
+ if (call_depth == 0 && dso != nullptr) {
+ g_array.recompact();
+ }
+
+ atexit_unlock();
+
+ if (dso != nullptr) {
+ __unregister_atfork(dso);
+ } else {
+ // If called via exit(), flush output of all open files.
+ __libc_stdio_cleanup();
+ }
+}
diff --git a/libc/stdlib/atexit.h b/libc/bionic/atexit.h
similarity index 90%
rename from libc/stdlib/atexit.h
rename to libc/bionic/atexit.h
index e01bb34..8abcdc5 100644
--- a/libc/stdlib/atexit.h
+++ b/libc/bionic/atexit.h
@@ -30,5 +30,13 @@
*
*/
-int __cxa_atexit(void (*)(void *), void *, void *);
-void __cxa_finalize(void *);
+#pragma once
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+int __cxa_atexit(void (*)(void*), void*, void*);
+void __cxa_finalize(void*);
+
+__END_DECLS
diff --git a/libc/stdlib/atexit.c b/libc/stdlib/atexit.c
deleted file mode 100644
index 0efb118..0000000
--- a/libc/stdlib/atexit.c
+++ /dev/null
@@ -1,200 +0,0 @@
-/* $OpenBSD: atexit.c,v 1.20 2014/07/11 09:51:37 kettenis Exp $ */
-/*
- * Copyright (c) 2002 Daniel Hartmeier
- * 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 HOLDERS 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 "atexit.h"
-
-#include <pthread.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/prctl.h>
-#include <sys/types.h>
-
-static pthread_mutex_t g_atexit_lock = PTHREAD_MUTEX_INITIALIZER;
-#define _ATEXIT_LOCK() pthread_mutex_lock(&g_atexit_lock)
-#define _ATEXIT_UNLOCK() pthread_mutex_unlock(&g_atexit_lock)
-
-struct atexit {
- struct atexit *next; /* next in list */
- int ind; /* next index in this table */
- int max; /* max entries >= ATEXIT_SIZE */
- struct atexit_fn {
- void (*fn_ptr)(void *);
- void *fn_arg; /* argument for CXA callback */
- void *fn_dso; /* shared module handle */
- } fns[1]; /* the table itself */
-};
-
-static struct atexit *__atexit;
-static int restartloop;
-
-/* BEGIN android-changed: __unregister_atfork is used by __cxa_finalize */
-extern void __unregister_atfork(void* dso);
-/* END android-changed */
-
-/*
- * Function pointers are stored in a linked list of pages. The list
- * is initially empty, and pages are allocated on demand. The first
- * function pointer in the first allocated page (the last one in
- * the linked list) is reserved for the cleanup function.
- *
- * Outside the following functions, all pages are mprotect()'ed
- * to prevent unintentional/malicious corruption.
- */
-
-/*
- * Register a function to be performed at exit or when a shared object
- * with the given dso handle is unloaded dynamically. Also used as
- * the backend for atexit(). For more info on this API, see:
- *
- * http://www.codesourcery.com/cxx-abi/abi.html#dso-dtor
- */
-int
-__cxa_atexit(void (*func)(void *), void *arg, void *dso)
-{
- struct atexit_fn *fnp;
- size_t pgsize = getpagesize();
- int ret = -1;
-
- if (pgsize < sizeof(struct atexit))
- return (-1);
- _ATEXIT_LOCK();
- struct atexit *p = __atexit;
- if (p != NULL) {
- if (p->ind + 1 >= p->max)
- p = NULL;
- else if (mprotect(p, pgsize, PROT_READ | PROT_WRITE))
- goto unlock;
- }
- if (p == NULL) {
- p = mmap(NULL, pgsize, PROT_READ | PROT_WRITE,
- MAP_ANON | MAP_PRIVATE, -1, 0);
- if (p == MAP_FAILED)
- goto unlock;
-/* BEGIN android-changed */
- prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, p, pgsize,
- "atexit handlers");
-/* END android-changed */
- if (__atexit == NULL) {
- memset(&p->fns[0], 0, sizeof(p->fns[0]));
- p->ind = 1;
- } else
- p->ind = 0;
- p->max = (pgsize - ((char *)&p->fns[0] - (char *)p)) /
- sizeof(p->fns[0]);
- p->next = __atexit;
- __atexit = p;
- }
- fnp = &p->fns[p->ind++];
- fnp->fn_ptr = func;
- fnp->fn_arg = arg;
- fnp->fn_dso = dso;
- if (mprotect(p, pgsize, PROT_READ))
- goto unlock;
- restartloop = 1;
- ret = 0;
-unlock:
- _ATEXIT_UNLOCK();
- return (ret);
-}
-
-/*
- * Call all handlers registered with __cxa_atexit() for the shared
- * object owning 'dso'.
- * Note: if 'dso' is NULL, then all remaining handlers are called.
- */
-void
-__cxa_finalize(void *dso)
-{
- struct atexit *p, *q;
- struct atexit_fn fn;
- int n, pgsize = getpagesize();
- static int call_depth;
-
- _ATEXIT_LOCK();
- call_depth++;
-
-restart:
- restartloop = 0;
- for (p = __atexit; p != NULL; p = p->next) {
- for (n = p->ind; --n >= 0;) {
- if (p->fns[n].fn_ptr == NULL)
- continue; /* already called */
- if (dso != NULL && dso != p->fns[n].fn_dso)
- continue; /* wrong DSO */
-
- /*
- * Mark handler as having been already called to avoid
- * dupes and loops, then call the appropriate function.
- */
- fn = p->fns[n];
- if (mprotect(p, pgsize, PROT_READ | PROT_WRITE) == 0) {
- p->fns[n].fn_ptr = NULL;
- mprotect(p, pgsize, PROT_READ);
- }
- _ATEXIT_UNLOCK();
- (*fn.fn_ptr)(fn.fn_arg);
- _ATEXIT_LOCK();
- if (restartloop)
- goto restart;
- }
- }
-
- call_depth--;
-
- /*
- * If called via exit(), unmap the pages since we have now run
- * all the handlers. We defer this until calldepth == 0 so that
- * we don't unmap things prematurely if called recursively.
- */
- if (dso == NULL && call_depth == 0) {
- for (p = __atexit; p != NULL; ) {
- q = p;
- p = p->next;
- munmap(q, pgsize);
- }
- __atexit = NULL;
- }
- _ATEXIT_UNLOCK();
-
- /* If called via exit(), flush output of all open files. */
- if (dso == NULL) {
- extern void __libc_stdio_cleanup(void);
- __libc_stdio_cleanup();
- }
-
- /* BEGIN android-changed: call __unregister_atfork if dso is not null */
- if (dso != NULL) {
- __unregister_atfork(dso);
- }
- /* END android-changed */
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index b840f36..f39aff0 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -83,6 +83,7 @@
defaults: ["bionic_tests_defaults"],
srcs: [
"__aeabi_read_tp_test.cpp",
+ "__cxa_atexit_test.cpp",
"alloca_test.cpp",
"android_get_device_api_level.cpp",
"arpa_inet_test.cpp",
diff --git a/tests/__cxa_atexit_test.cpp b/tests/__cxa_atexit_test.cpp
new file mode 100644
index 0000000..6a122d1
--- /dev/null
+++ b/tests/__cxa_atexit_test.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 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 <cxxabi.h>
+#include <gtest/gtest.h>
+
+TEST(__cxa_atexit, simple) {
+ int counter = 0;
+
+ __cxxabiv1::__cxa_atexit([](void* arg) { ++*static_cast<int*>(arg); }, &counter, &counter);
+
+ __cxxabiv1::__cxa_finalize(&counter);
+ ASSERT_EQ(counter, 1);
+
+ // The handler won't be called twice.
+ __cxxabiv1::__cxa_finalize(&counter);
+ ASSERT_EQ(counter, 1);
+}
+
+TEST(__cxa_atexit, order) {
+ static std::vector<int> actual;
+
+ char handles[2];
+
+ auto append_to_actual = [](void* arg) {
+ int* idx = static_cast<int*>(arg);
+ actual.push_back(*idx);
+ delete idx;
+ };
+
+ for (int i = 0; i < 500; ++i) {
+ __cxxabiv1::__cxa_atexit(append_to_actual, new int{i}, &handles[i % 2]);
+ }
+
+ __cxxabiv1::__cxa_finalize(&handles[0]);
+
+ for (int i = 500; i < 750; ++i) {
+ __cxxabiv1::__cxa_atexit(append_to_actual, new int{i}, &handles[1]);
+ }
+
+ __cxxabiv1::__cxa_finalize(&handles[1]);
+
+ std::vector<int> expected;
+ for (int i = 498; i >= 0; i -= 2) expected.push_back(i);
+ for (int i = 749; i >= 500; --i) expected.push_back(i);
+ for (int i = 499; i >= 1; i -= 2) expected.push_back(i);
+
+ ASSERT_EQ(expected.size(), actual.size());
+ for (size_t i = 0; i < expected.size(); ++i) {
+ ASSERT_EQ(expected[i], actual[i]) << "index=" << i;
+ }
+}