Add BionicAllocator::memalign
Bionic needs this functionality to allocate a TLS segment with greater
than 16-byte alignment. For simplicity, this allocator only supports up
to one page of alignment.
The memory layout changes slightly when allocating an object of exactly
PAGE_SIZE alignment. Instead of allocating the page_info header at the
start of the page containing the pointer, it is allocated at the start
of the preceding page.
Bug: http://b/78026329
Test: linker-unit-tests{32,64}
Change-Id: I1c8d1cd7ca72d113bced5ee15ba8d831426b0081
diff --git a/libc/bionic/bionic_allocator.cpp b/libc/bionic/bionic_allocator.cpp
index a933212..d9302ad 100644
--- a/libc/bionic/bionic_allocator.cpp
+++ b/libc/bionic/bionic_allocator.cpp
@@ -31,6 +31,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
+#include <sys/param.h>
#include <sys/prctl.h>
#include <unistd.h>
@@ -38,6 +39,7 @@
#include <async_safe/log.h>
+#include "private/bionic_macros.h"
#include "private/bionic_page.h"
//
@@ -262,8 +264,14 @@
allocators_ = allocators;
}
-void* BionicAllocator::alloc_mmap(size_t size) {
- size_t allocated_size = PAGE_END(size + kPageInfoSize);
+void* BionicAllocator::alloc_mmap(size_t align, size_t size) {
+ size_t header_size = __BIONIC_ALIGN(kPageInfoSize, align);
+ size_t allocated_size;
+ if (__builtin_add_overflow(header_size, size, &allocated_size) ||
+ PAGE_END(allocated_size) < allocated_size) {
+ async_safe_fatal("overflow trying to alloc %zu bytes", size);
+ }
+ allocated_size = PAGE_END(allocated_size);
void* map_ptr = mmap(nullptr, allocated_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0);
@@ -273,23 +281,19 @@
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, map_ptr, allocated_size, "bionic_alloc_lob");
- page_info* info = reinterpret_cast<page_info*>(map_ptr);
+ void* result = static_cast<char*>(map_ptr) + header_size;
+ page_info* info = get_page_info_unchecked(result);
memcpy(info->signature, kSignature, sizeof(kSignature));
info->type = kLargeObject;
info->allocated_size = allocated_size;
- return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(info) +
- kPageInfoSize);
+ return result;
}
-void* BionicAllocator::alloc(size_t size) {
- // treat alloc(0) as alloc(1)
- if (size == 0) {
- size = 1;
- }
+inline void* BionicAllocator::alloc_impl(size_t align, size_t size) {
if (size > kSmallObjectMaxSize) {
- return alloc_mmap(size);
+ return alloc_mmap(align, size);
}
uint16_t log2_size = log2(size);
@@ -301,8 +305,33 @@
return get_small_object_allocator(log2_size)->alloc();
}
-page_info* BionicAllocator::get_page_info(void* ptr) {
- page_info* info = reinterpret_cast<page_info*>(PAGE_START(reinterpret_cast<size_t>(ptr)));
+void* BionicAllocator::alloc(size_t size) {
+ // treat alloc(0) as alloc(1)
+ if (size == 0) {
+ size = 1;
+ }
+ return alloc_impl(16, size);
+}
+
+void* BionicAllocator::memalign(size_t align, size_t size) {
+ // The Bionic allocator only supports alignment up to one page, which is good
+ // enough for ELF TLS.
+ align = MIN(align, PAGE_SIZE);
+ align = MAX(align, 16);
+ if (!powerof2(align)) {
+ align = BIONIC_ROUND_UP_POWER_OF_2(align);
+ }
+ size = MAX(size, align);
+ return alloc_impl(align, size);
+}
+
+inline page_info* BionicAllocator::get_page_info_unchecked(void* ptr) {
+ uintptr_t header_page = PAGE_START(reinterpret_cast<size_t>(ptr) - kPageInfoSize);
+ return reinterpret_cast<page_info*>(header_page);
+}
+
+inline page_info* BionicAllocator::get_page_info(void* ptr) {
+ page_info* info = get_page_info_unchecked(ptr);
if (memcmp(info->signature, kSignature, sizeof(kSignature)) != 0) {
async_safe_fatal("invalid pointer %p (page signature mismatch)", ptr);
}
@@ -325,7 +354,7 @@
size_t old_size = 0;
if (info->type == kLargeObject) {
- old_size = info->allocated_size - kPageInfoSize;
+ old_size = info->allocated_size - (static_cast<char*>(ptr) - reinterpret_cast<char*>(info));
} else {
BionicSmallObjectAllocator* allocator = get_small_object_allocator(info->type);
if (allocator != info->allocator_addr) {