From d597d263bc32422402d4810ce4ec070f0227c2f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Ivanov Date: Mon, 5 May 2014 16:49:04 -0700 Subject: [PATCH] Refactor linker allocator Makes it reusable for different fixed sized and not very big structures ( 100644 linker/linker.cpp create mode 100644 linker/linker_allocator.cpp create mode 100644 linker/linker_allocator.h create mode 100644 linker/tests/Android.mk create mode 100644 linker/tests/linker_allocator_test.cpp diff --git a/linker/Android.mk b/linker/Android.mk index f0e6c139a..d2bcfaf0b 100644 --- a/linker/Android.mk +++ b/linker/Android.mk @@ -6,6 +6,7 @@ LOCAL_SRC_FILES:= \ debugger.cpp \ dlfcn.cpp \ linker.cpp \ + linker_allocator.cpp \ linker_environ.cpp \ linker_phdr.cpp \ rt.cpp \ @@ -67,3 +68,5 @@ LOCAL_INSTALLED_MODULE_STEM := 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 1f32f6d09..4be68d106 --- 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 @@ static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf); // 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 gSoInfoAllocator; static soinfo* solist = &libdl_info; static soinfo* sonext = &libdl_info; @@ -270,56 +265,13 @@ void notify_gdb_of_libraries() { 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(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 @@ static void soinfo_free(soinfo* si) { if (si == sonext) { sonext = prev; } - si->next = gSoInfoFreeList; - gSoInfoFreeList = si; + + gSoInfoAllocator.free(si); } @@ -795,8 +747,8 @@ static int soinfo_unload(soinfo* si) { munmap(reinterpret_cast(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 @@ soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo) 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 @@ void soinfo::CallFunction(const char* function_name __unused, linker_function_t // 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 @@ static ElfW(Addr) __linker_init_post_relocation(KernelArgumentBlock& args, ElfW( 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 @@ extern "C" ElfW(Addr) __linker_init(void* raw_args) { 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 000000000..805844fc2 --- /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 +#include +#include + +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(free_block_list_); + if (block_info->num_free_blocks > 1) { + FreeBlockInfo* next_block_info = reinterpret_cast( + reinterpret_cast(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(block) - page->bytes; + + if (offset % block_size_ != 0) { + abort(); + } + + FreeBlockInfo* block_info = reinterpret_cast(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(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(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(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 000000000..e5b63c593 --- /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 +#include +#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 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 +class LinkerAllocator { + public: + LinkerAllocator() : block_allocator_() {} + void init() { block_allocator_.init(sizeof(T)); } + T* alloc() { return reinterpret_cast(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 000000000..600fe6910 --- /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 000000000..ccbdce6f0 --- /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 +#include +#include + +#include + +#include "../linker_allocator.h" + +#include + +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 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 allocator; + allocator.init(); + + char* ptr1 = reinterpret_cast(allocator.alloc()); + char* ptr2 = reinterpret_cast(allocator.alloc()); + + ASSERT_TRUE(ptr1 != nullptr); + ASSERT_TRUE(ptr2 != nullptr); + ASSERT_EQ(ptr1+2*sizeof(void*), ptr2); +} + +TEST(linker_allocator, test_larger) { + LinkerAllocator 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 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; idummy_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 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; idummy_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"); +} +