LibC: Expand region for global destructors when it reaches capacity

In 553361d we started mprotecting the atexit handlers when they are not
being modified or executed. As part of that commit, we unintentionally
changed the max number of global destructors from 1024 to 256 (on x86,
only 128 on x86_64). This patch expands the initial size of the global
destructors page to 2 pages from 1, and allows the pool to be expanded
at runtime by mapping a new set of pages and copying the AtExitEntries
over.
This commit is contained in:
Andrew Kaster 2021-09-05 11:59:38 -06:00 committed by Andreas Kling
parent 7589cc2494
commit fe701052ea
Notes: sideshowbarker 2024-07-18 04:39:28 +09:00

View File

@ -1,15 +1,17 @@
/*
* Copyright (c) 2019-2020, Andrew Kaster <akaster@serenityos.org>
* Copyright (c) 2019-2021, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/Debug.h>
#include <AK/Format.h>
#include <LibC/bits/pthread_integration.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/internals.h>
#include <sys/mman.h>
#include <unistd.h>
@ -23,7 +25,8 @@ struct AtExitEntry {
bool has_been_called { false };
};
static constexpr size_t max_atexit_entry_count = PAGE_SIZE / sizeof(AtExitEntry);
// We'll re-allocate the region if it ends up being too small at runtime
static size_t atexit_entry_region_size = 2 * PAGE_SIZE;
static AtExitEntry* atexit_entries;
static size_t atexit_entry_count = 0;
@ -31,7 +34,7 @@ static pthread_mutex_t atexit_mutex = __PTHREAD_MUTEX_INITIALIZER;
static void lock_atexit_handlers()
{
if (mprotect(atexit_entries, PAGE_SIZE, PROT_READ) < 0) {
if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ) < 0) {
perror("lock_atexit_handlers");
_exit(1);
}
@ -39,7 +42,7 @@ static void lock_atexit_handlers()
static void unlock_atexit_handlers()
{
if (mprotect(atexit_entries, PAGE_SIZE, PROT_READ | PROT_WRITE) < 0) {
if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ | PROT_WRITE) < 0) {
perror("unlock_atexit_handlers");
_exit(1);
}
@ -49,13 +52,9 @@ int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle
{
__pthread_mutex_lock(&atexit_mutex);
if (atexit_entry_count >= max_atexit_entry_count) {
__pthread_mutex_unlock(&atexit_mutex);
return -1;
}
// allocate initial atexit region
if (!atexit_entries) {
atexit_entries = (AtExitEntry*)mmap(nullptr, PAGE_SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
atexit_entries = (AtExitEntry*)mmap(nullptr, atexit_entry_region_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (atexit_entries == MAP_FAILED) {
__pthread_mutex_unlock(&atexit_mutex);
perror("__cxa_atexit mmap");
@ -63,6 +62,30 @@ int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle
}
}
// reallocate atexit region, increasing size by PAGE_SIZE
if ((atexit_entry_count) >= (atexit_entry_region_size / sizeof(AtExitEntry))) {
if (Checked<size_t>::addition_would_overflow(atexit_entry_region_size, PAGE_SIZE)) {
__pthread_mutex_unlock(&atexit_mutex);
return -1;
}
dbgln_if(GLOBAL_DTORS_DEBUG, "__cxa_atexit: Growing exit handler region from {} to {}", atexit_entry_region_size, atexit_entry_region_size + PAGE_SIZE);
size_t new_atexit_region_size = atexit_entry_region_size + PAGE_SIZE;
auto* new_atexit_entries = (AtExitEntry*)mmap(nullptr, new_atexit_region_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (new_atexit_entries == MAP_FAILED) {
__pthread_mutex_unlock(&atexit_mutex);
perror("__cxa_atexit mmap (new size)");
return -1;
}
memcpy(new_atexit_entries, atexit_entries, atexit_entry_region_size);
if (munmap(atexit_entries, atexit_entry_region_size) < 0) {
perror("__cxa_atexit munmap old region");
// leak the old region on failure
}
atexit_entries = new_atexit_entries;
atexit_entry_region_size = new_atexit_region_size;
}
unlock_atexit_handlers();
atexit_entries[atexit_entry_count++] = { exit_function, parameter, dso_handle, false };
lock_atexit_handlers();