Home | History | Annotate | Download | only in linker
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "linker_block_allocator.h"
     18 #include <inttypes.h>
     19 #include <string.h>
     20 #include <sys/mman.h>
     21 #include <unistd.h>
     22 
     23 #include "private/bionic_prctl.h"
     24 
     25 // the multiplier should be power of 2
     26 static constexpr size_t round_up(size_t size, size_t multiplier) {
     27   return (size + (multiplier - 1)) & ~(multiplier-1);
     28 }
     29 
     30 struct LinkerBlockAllocatorPage {
     31   LinkerBlockAllocatorPage* next;
     32   uint8_t bytes[PAGE_SIZE - 16] __attribute__((aligned(16)));
     33 };
     34 
     35 struct FreeBlockInfo {
     36   void* next_block;
     37   size_t num_free_blocks;
     38 };
     39 
     40 LinkerBlockAllocator::LinkerBlockAllocator(size_t block_size)
     41   : block_size_(
     42       round_up(block_size < sizeof(FreeBlockInfo) ? sizeof(FreeBlockInfo) : block_size, 16)),
     43     page_list_(nullptr),
     44     free_block_list_(nullptr)
     45 {}
     46 
     47 void* LinkerBlockAllocator::alloc() {
     48   if (free_block_list_ == nullptr) {
     49     create_new_page();
     50   }
     51 
     52   FreeBlockInfo* block_info = reinterpret_cast<FreeBlockInfo*>(free_block_list_);
     53   if (block_info->num_free_blocks > 1) {
     54     FreeBlockInfo* next_block_info = reinterpret_cast<FreeBlockInfo*>(
     55       reinterpret_cast<char*>(free_block_list_) + block_size_);
     56     next_block_info->next_block = block_info->next_block;
     57     next_block_info->num_free_blocks = block_info->num_free_blocks - 1;
     58     free_block_list_ = next_block_info;
     59   } else {
     60     free_block_list_ = block_info->next_block;
     61   }
     62 
     63   memset(block_info, 0, block_size_);
     64 
     65   return block_info;
     66 }
     67 
     68 void LinkerBlockAllocator::free(void* block) {
     69   if (block == nullptr) {
     70     return;
     71   }
     72 
     73   LinkerBlockAllocatorPage* page = find_page(block);
     74 
     75   if (page == nullptr) {
     76     abort();
     77   }
     78 
     79   ssize_t offset = reinterpret_cast<uint8_t*>(block) - page->bytes;
     80 
     81   if (offset % block_size_ != 0) {
     82     abort();
     83   }
     84 
     85   memset(block, 0, block_size_);
     86 
     87   FreeBlockInfo* block_info = reinterpret_cast<FreeBlockInfo*>(block);
     88 
     89   block_info->next_block = free_block_list_;
     90   block_info->num_free_blocks = 1;
     91 
     92   free_block_list_ = block_info;
     93 }
     94 
     95 void LinkerBlockAllocator::protect_all(int prot) {
     96   for (LinkerBlockAllocatorPage* page = page_list_; page != nullptr; page = page->next) {
     97     if (mprotect(page, PAGE_SIZE, prot) == -1) {
     98       abort();
     99     }
    100   }
    101 }
    102 
    103 void LinkerBlockAllocator::create_new_page() {
    104   static_assert(sizeof(LinkerBlockAllocatorPage) == PAGE_SIZE,
    105                 "Invalid sizeof(LinkerBlockAllocatorPage)");
    106 
    107   LinkerBlockAllocatorPage* page = reinterpret_cast<LinkerBlockAllocatorPage*>(
    108       mmap(nullptr, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0));
    109 
    110   if (page == MAP_FAILED) {
    111     abort(); // oom
    112   }
    113 
    114   prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, page, PAGE_SIZE, "linker_alloc");
    115 
    116   memset(page, 0, PAGE_SIZE);
    117 
    118   FreeBlockInfo* first_block = reinterpret_cast<FreeBlockInfo*>(page->bytes);
    119   first_block->next_block = free_block_list_;
    120   first_block->num_free_blocks = (PAGE_SIZE - sizeof(LinkerBlockAllocatorPage*))/block_size_;
    121 
    122   free_block_list_ = first_block;
    123 
    124   page->next = page_list_;
    125   page_list_ = page;
    126 }
    127 
    128 LinkerBlockAllocatorPage* LinkerBlockAllocator::find_page(void* block) {
    129   if (block == nullptr) {
    130     abort();
    131   }
    132 
    133   LinkerBlockAllocatorPage* page = page_list_;
    134   while (page != nullptr) {
    135     const uint8_t* page_ptr = reinterpret_cast<const uint8_t*>(page);
    136     if (block >= (page_ptr + sizeof(page->next)) && block < (page_ptr + PAGE_SIZE)) {
    137       return page;
    138     }
    139 
    140     page = page->next;
    141   }
    142 
    143   abort();
    144 }
    145