Refactor linker allocator
Makes it reusable for different fixed sized and not very
big structures (<PAGE_SIZE).
Change-Id: Id5ec13fc6541b1935ef7fe3671c22b98685abbae
diff --git a/linker/Android.mk b/linker/Android.mk
index f0e6c13..d2bcfaf 100644
--- a/linker/Android.mk
+++ b/linker/Android.mk
@@ -6,6 +6,7 @@
debugger.cpp \
dlfcn.cpp \
linker.cpp \
+ linker_allocator.cpp \
linker_environ.cpp \
linker_phdr.cpp \
rt.cpp \
@@ -67,3 +68,5 @@
LOCAL_INTERMEDIATE_TARGETS :=
include $(LOCAL_PATH)/linker_executable.mk
endif
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/linker/linker.cpp b/linker/linker.cpp
old mode 100755
new mode 100644
index 1f32f6d..4be68d1
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -48,6 +48,7 @@
#include "linker_debug.h"
#include "linker_environ.h"
#include "linker_phdr.h"
+#include "linker_allocator.h"
/* >>> IMPORTANT NOTE - READ ME BEFORE MODIFYING <<<
*
@@ -70,14 +71,8 @@
// We can't use malloc(3) in the dynamic linker. We use a linked list of anonymous
// maps, each a single page in size. The pages are broken up into as many struct soinfo
-// objects as will fit, and they're all threaded together on a free list.
-#define SOINFO_PER_POOL ((PAGE_SIZE - sizeof(soinfo_pool_t*)) / sizeof(soinfo))
-struct soinfo_pool_t {
- soinfo_pool_t* next;
- soinfo info[SOINFO_PER_POOL];
-};
-static struct soinfo_pool_t* gSoInfoPools = NULL;
-static soinfo* gSoInfoFreeList = NULL;
+// objects as will fit.
+static LinkerAllocator<soinfo> gSoInfoAllocator;
static soinfo* solist = &libdl_info;
static soinfo* sonext = &libdl_info;
@@ -270,56 +265,13 @@
rtld_db_dlactivity();
}
-static bool ensure_free_list_non_empty() {
- if (gSoInfoFreeList != NULL) {
- return true;
- }
-
- // Allocate a new pool.
- soinfo_pool_t* pool = reinterpret_cast<soinfo_pool_t*>(mmap(NULL, sizeof(*pool),
- PROT_READ|PROT_WRITE,
- MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
- if (pool == MAP_FAILED) {
- return false;
- }
-
- // Add the pool to our list of pools.
- pool->next = gSoInfoPools;
- gSoInfoPools = pool;
-
- // Chain the entries in the new pool onto the free list.
- gSoInfoFreeList = &pool->info[0];
- soinfo* next = NULL;
- for (int i = SOINFO_PER_POOL - 1; i >= 0; --i) {
- pool->info[i].next = next;
- next = &pool->info[i];
- }
-
- return true;
-}
-
-static void set_soinfo_pool_protection(int protection) {
- for (soinfo_pool_t* p = gSoInfoPools; p != NULL; p = p->next) {
- if (mprotect(p, sizeof(*p), protection) == -1) {
- abort(); // Can't happen.
- }
- }
-}
-
static soinfo* soinfo_alloc(const char* name) {
if (strlen(name) >= SOINFO_NAME_LEN) {
DL_ERR("library name \"%s\" too long", name);
return NULL;
}
- if (!ensure_free_list_non_empty()) {
- DL_ERR("out of memory when loading \"%s\"", name);
- return NULL;
- }
-
- // Take the head element off the free list.
- soinfo* si = gSoInfoFreeList;
- gSoInfoFreeList = gSoInfoFreeList->next;
+ soinfo* si = gSoInfoAllocator.alloc();
// Initialize the new element.
memset(si, 0, sizeof(soinfo));
@@ -358,8 +310,8 @@
if (si == sonext) {
sonext = prev;
}
- si->next = gSoInfoFreeList;
- gSoInfoFreeList = si;
+
+ gSoInfoAllocator.free(si);
}
@@ -795,8 +747,8 @@
munmap(reinterpret_cast<void*>(si->base), si->size);
notify_gdb_of_unload(si);
- soinfo_free(si);
si->ref_count = 0;
+ soinfo_free(si);
} else {
si->ref_count--;
TRACE("not unloading '%s', decrementing ref_count to %zd", si->name, si->ref_count);
@@ -823,19 +775,19 @@
DL_ERR("invalid extended flags to android_dlopen_ext: %x", extinfo->flags);
return NULL;
}
- set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
+ gSoInfoAllocator.protect_all(PROT_READ | PROT_WRITE);
soinfo* si = find_library(name, extinfo);
if (si != NULL) {
si->CallConstructors();
}
- set_soinfo_pool_protection(PROT_READ);
+ gSoInfoAllocator.protect_all(PROT_READ);
return si;
}
int do_dlclose(soinfo* si) {
- set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
+ gSoInfoAllocator.protect_all(PROT_READ | PROT_WRITE);
int result = soinfo_unload(si);
- set_soinfo_pool_protection(PROT_READ);
+ gSoInfoAllocator.protect_all(PROT_READ);
return result;
}
@@ -1449,7 +1401,7 @@
// The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
// are still writable. This happens with our debug malloc (see http://b/7941716).
- set_soinfo_pool_protection(PROT_READ | PROT_WRITE);
+ gSoInfoAllocator.protect_all(PROT_READ | PROT_WRITE);
}
void soinfo::CallPreInitConstructors() {
@@ -2000,6 +1952,11 @@
ldpreload_env = linker_env_get("LD_PRELOAD");
}
+ // Linker does not call constructors for its own
+ // global variables so we need to initialize
+ // the allocator explicitly.
+ gSoInfoAllocator.init();
+
INFO("[ android linker & debugger ]");
soinfo* si = soinfo_alloc(args.argv[0]);
@@ -2217,7 +2174,7 @@
args.abort_message_ptr = &gAbortMessage;
ElfW(Addr) start_address = __linker_init_post_relocation(args, linker_addr);
- set_soinfo_pool_protection(PROT_READ);
+ gSoInfoAllocator.protect_all(PROT_READ);
// Return the address that the calling assembly stub should jump to.
return start_address;
diff --git a/linker/linker_allocator.cpp b/linker/linker_allocator.cpp
new file mode 100644
index 0000000..805844fc
--- /dev/null
+++ b/linker/linker_allocator.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+#include "linker_allocator.h"
+#include <inttypes.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+struct LinkerAllocatorPage {
+ LinkerAllocatorPage* next;
+ uint8_t bytes[PAGE_SIZE-sizeof(LinkerAllocatorPage*)];
+};
+
+struct FreeBlockInfo {
+ void* next_block;
+ size_t num_free_blocks;
+};
+
+LinkerBlockAllocator::LinkerBlockAllocator()
+ : block_size_(0),
+ page_list_(nullptr),
+ free_block_list_(nullptr)
+{}
+
+void LinkerBlockAllocator::init(size_t block_size) {
+ block_size_ = block_size < sizeof(FreeBlockInfo) ? sizeof(FreeBlockInfo) : block_size;
+}
+
+
+void* LinkerBlockAllocator::alloc() {
+ if (free_block_list_ == nullptr) {
+ create_new_page();
+ } else {
+ protect_page(free_block_list_, PROT_READ | PROT_WRITE);
+ }
+
+ FreeBlockInfo* block_info = reinterpret_cast<FreeBlockInfo*>(free_block_list_);
+ if (block_info->num_free_blocks > 1) {
+ FreeBlockInfo* next_block_info = reinterpret_cast<FreeBlockInfo*>(
+ reinterpret_cast<char*>(free_block_list_) + block_size_);
+ next_block_info->next_block = block_info->next_block;
+ next_block_info->num_free_blocks = block_info->num_free_blocks - 1;
+ free_block_list_ = next_block_info;
+ } else {
+ free_block_list_ = block_info->next_block;
+ }
+
+ block_info->next_block = nullptr;
+ block_info->num_free_blocks = 0;
+
+ return block_info;
+}
+
+void LinkerBlockAllocator::free(void* block) {
+ if (block == nullptr) {
+ return;
+ }
+
+ LinkerAllocatorPage* page = find_page(block);
+
+ if (page == nullptr) {
+ abort();
+ }
+
+ ssize_t offset = reinterpret_cast<uint8_t*>(block) - page->bytes;
+
+ if (offset % block_size_ != 0) {
+ abort();
+ }
+
+ FreeBlockInfo* block_info = reinterpret_cast<FreeBlockInfo*>(block);
+
+ protect_page(block_info, PROT_READ | PROT_WRITE);
+ block_info->next_block = free_block_list_;
+ block_info->num_free_blocks = 1;
+ protect_page(block_info, PROT_READ);
+
+ free_block_list_ = block_info;
+}
+
+void LinkerBlockAllocator::protect_all(int prot) {
+ for (LinkerAllocatorPage* page = page_list_; page != nullptr; page = page->next) {
+ if (mprotect(page, PAGE_SIZE, prot) == -1) {
+ abort();
+ }
+ }
+}
+
+void LinkerBlockAllocator::protect_page(void* block, int prot) {
+ LinkerAllocatorPage* page = find_page(block);
+ if (page == nullptr || mprotect(page, PAGE_SIZE, prot) == -1) {
+ abort();
+ }
+}
+
+
+void LinkerBlockAllocator::create_new_page() {
+ LinkerAllocatorPage* page = reinterpret_cast<LinkerAllocatorPage*>(mmap(nullptr, PAGE_SIZE,
+ PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
+ if (page == MAP_FAILED) {
+ abort(); // oom
+ }
+
+ FreeBlockInfo* first_block = reinterpret_cast<FreeBlockInfo*>(page->bytes);
+ first_block->next_block = free_block_list_;
+ first_block->num_free_blocks = (PAGE_SIZE - sizeof(LinkerAllocatorPage*))/block_size_;
+
+ free_block_list_ = first_block;
+
+ page->next = page_list_;
+ page_list_ = page;
+}
+
+LinkerAllocatorPage* LinkerBlockAllocator::find_page(void* block) {
+ if (block == nullptr) {
+ abort();
+ }
+
+ LinkerAllocatorPage* page = page_list_;
+ const uint8_t* page_ptr = reinterpret_cast<const uint8_t*>(page);
+ while (page != nullptr) {
+ if (block >= (page_ptr + sizeof(page->next)) && block < (page_ptr + PAGE_SIZE)) {
+ return page;
+ }
+
+ page = page->next;
+ }
+
+ abort();
+}
diff --git a/linker/linker_allocator.h b/linker/linker_allocator.h
new file mode 100644
index 0000000..e5b63c5
--- /dev/null
+++ b/linker/linker_allocator.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#ifndef __LINKER_ALLOCATOR_H
+#define __LINKER_ALLOCATOR_H
+
+#include <stdlib.h>
+#include <limits.h>
+#include "private/bionic_macros.h"
+
+struct LinkerAllocatorPage;
+
+/*
+ * This class is a non-template version of the LinkerAllocator
+ * It keeps code inside .cpp file by keeping the interface
+ * template-free.
+ *
+ * Please use LinkerAllocator<type> where possible (everywhere).
+ */
+class LinkerBlockAllocator {
+ public:
+ LinkerBlockAllocator();
+
+ void init(size_t block_size);
+ void* alloc();
+ void free(void* block);
+ void protect_page(void* block, int prot);
+ void protect_all(int prot);
+
+ private:
+ void create_new_page();
+ LinkerAllocatorPage* find_page(void* block);
+
+ size_t block_size_;
+ LinkerAllocatorPage* page_list_;
+ void* free_block_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(LinkerBlockAllocator);
+};
+
+/*
+ * A simple allocator for the dynamic linker. An allocator allocates instances
+ * of a single fixed-size type. Allocations are backed by page-sized private
+ * anonymous mmaps.
+ */
+template<typename T>
+class LinkerAllocator {
+ public:
+ LinkerAllocator() : block_allocator_() {}
+ void init() { block_allocator_.init(sizeof(T)); }
+ T* alloc() { return reinterpret_cast<T*>(block_allocator_.alloc()); }
+ void free(T* t) { block_allocator_.free(t); }
+ void protect_page(T* t, int prot) { block_allocator_.protect_page(t, prot); }
+ void protect_all(int prot) { block_allocator_.protect_all(prot); }
+ private:
+ LinkerBlockAllocator block_allocator_;
+ DISALLOW_COPY_AND_ASSIGN(LinkerAllocator);
+};
+#endif // __LINKER_ALLOCATOR_H
diff --git a/linker/tests/Android.mk b/linker/tests/Android.mk
new file mode 100644
index 0000000..600fe69
--- /dev/null
+++ b/linker/tests/Android.mk
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2012 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.
+#
+
+ifneq ($(BUILD_TINY_ANDROID),true)
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MULTILIB := both
+LOCAL_MODULE := linker-unit-tests
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+
+LOCAL_CFLAGS += -g -Wall -Wextra -Werror -std=gnu++11
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../libc/
+
+LOCAL_SRC_FILES := \
+ linker_allocator_test.cpp \
+ ../linker_allocator.cpp
+
+include $(BUILD_NATIVE_TEST)
+
+endif # !BUILD_TINY_ANDROID
diff --git a/linker/tests/linker_allocator_test.cpp b/linker/tests/linker_allocator_test.cpp
new file mode 100644
index 0000000..ccbdce6
--- /dev/null
+++ b/linker/tests/linker_allocator_test.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include <gtest/gtest.h>
+
+#include "../linker_allocator.h"
+
+#include <unistd.h>
+
+namespace {
+
+struct test_struct_nominal {
+ void* pointer;
+ ssize_t value;
+};
+
+/*
+ * this one has size below allocator cap which is 2*sizeof(void*)
+ */
+struct test_struct_small {
+ char dummy_str[5];
+};
+
+/*
+ * 1009 byte struct (1009 is prime)
+ */
+struct test_struct_larger {
+ char dummy_str[1009];
+};
+
+static size_t kPageSize = sysconf(_SC_PAGE_SIZE);
+};
+
+TEST(linker_allocator, test_nominal) {
+ LinkerAllocator<test_struct_nominal> allocator;
+ allocator.init();
+
+ test_struct_nominal* ptr1 = allocator.alloc();
+ ASSERT_TRUE(ptr1 != nullptr);
+ test_struct_nominal* ptr2 = allocator.alloc();
+ ASSERT_TRUE(ptr2 != nullptr);
+ // they should be next to each other.
+ ASSERT_EQ(ptr1+1, ptr2);
+
+ ptr1->value = 42;
+
+ allocator.protect_page(ptr1, PROT_READ);
+
+ allocator.free(ptr1);
+ allocator.free(ptr2);
+}
+
+TEST(linker_allocator, test_small) {
+ LinkerAllocator<test_struct_small> allocator;
+ allocator.init();
+
+ char* ptr1 = reinterpret_cast<char*>(allocator.alloc());
+ char* ptr2 = reinterpret_cast<char*>(allocator.alloc());
+
+ ASSERT_TRUE(ptr1 != nullptr);
+ ASSERT_TRUE(ptr2 != nullptr);
+ ASSERT_EQ(ptr1+2*sizeof(void*), ptr2);
+}
+
+TEST(linker_allocator, test_larger) {
+ LinkerAllocator<test_struct_larger> allocator;
+ allocator.init();
+
+ test_struct_larger* ptr1 = allocator.alloc();
+ test_struct_larger* ptr2 = allocator.alloc();
+
+ ASSERT_TRUE(ptr1 != nullptr);
+ ASSERT_TRUE(ptr2 != nullptr);
+
+ ASSERT_EQ(ptr1+1, ptr2);
+
+ allocator.protect_page(ptr2, PROT_READ);
+
+ // lets allocate until we reach next page.
+ size_t n = kPageSize/sizeof(test_struct_larger) + 1 - 2;
+
+ for (size_t i=0; i<n; ++i) {
+ ASSERT_TRUE(allocator.alloc() != nullptr);
+ }
+
+}
+
+static void protect_one_page() {
+ LinkerAllocator<test_struct_larger> allocator;
+ allocator.init();
+
+ // number of allocs to reach the end of first page
+ size_t n = kPageSize/sizeof(test_struct_larger) - 1;
+ test_struct_larger* page1_ptr = allocator.alloc();
+
+ for (size_t i=0; i<n; ++i) {
+ allocator.alloc();
+ }
+
+ test_struct_larger* page2_ptr = allocator.alloc();
+
+ allocator.protect_page(page2_ptr, PROT_READ);
+
+ // check that we still have access to page1
+ page1_ptr->dummy_str[17] = 52;
+
+ fprintf(stderr, "trying to access protected page");
+
+ // this should result in segmentation fault
+ page2_ptr->dummy_str[12] = 3;
+}
+
+static void protect_all() {
+ LinkerAllocator<test_struct_larger> allocator;
+ allocator.init();
+
+ // number of allocs to reach the end of first page
+ size_t n = kPageSize/sizeof(test_struct_larger) - 1;
+ test_struct_larger* page1_ptr = allocator.alloc();
+
+ for (size_t i=0; i<n; ++i) {
+ allocator.alloc();
+ }
+
+ test_struct_larger* page2_ptr = allocator.alloc();
+ allocator.protect_all(PROT_READ);
+ allocator.protect_all(PROT_READ | PROT_WRITE);
+ // check access
+ page2_ptr->dummy_str[23] = 27;
+ page1_ptr->dummy_str[13] = 11;
+
+ allocator.protect_all(PROT_READ);
+ fprintf(stderr, "trying to access protected page");
+
+ // this should result in segmentation fault
+ page1_ptr->dummy_str[11] = 7;
+}
+
+TEST(linker_allocator, test_protect) {
+ testing::FLAGS_gtest_death_test_style = "threadsafe";
+ ASSERT_EXIT(protect_one_page(), testing::KilledBySignal(SIGSEGV), "trying to access protected page");
+ ASSERT_EXIT(protect_all(), testing::KilledBySignal(SIGSEGV), "trying to access protected page");
+}
+