blob: 3dcb3e26a7de4d2dafbd22ab8e196dc6a61bcc48 [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// 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 "starboard/elf_loader/program_table.h"
#include <sys/mman.h>
#include "starboard/common/log.h"
#include "starboard/common/string.h"
#include "starboard/elf_loader/evergreen_info.h"
#include "starboard/elf_loader/log.h"
#include "starboard/memory.h"
#include "starboard/string.h"
#define MAYBE_MAP_FLAG(x, from, to) (((x) & (from)) ? (to) : 0)
#define PFLAGS_TO_PROT(x) \
(MAYBE_MAP_FLAG((x), PF_X, PROT_EXEC) | \
MAYBE_MAP_FLAG((x), PF_R, PROT_READ) | \
MAYBE_MAP_FLAG((x), PF_W, PROT_WRITE))
namespace starboard {
namespace elf_loader {
ProgramTable::ProgramTable(
const CobaltExtensionMemoryMappedFileApi* memory_mapped_file_extension)
: phdr_num_(0),
phdr_mmap_(NULL),
phdr_table_(NULL),
phdr_size_(0),
load_start_(NULL),
load_size_(0),
base_memory_address_(0),
memory_mapped_file_extension_(memory_mapped_file_extension) {
#if SB_API_VERSION >= 16
SB_CHECK(kSbCanMapExecutableMemory)
<< "This module requires executable memory support!";
#else
#if !SB_CAN(MAP_EXECUTABLE_MEMORY)
SB_CHECK(false) << "This module requires "
"executable memory map support!";
#endif
#endif
}
bool ProgramTable::LoadProgramHeader(const Ehdr* elf_header, File* elf_file) {
if (!elf_header) {
SB_LOG(ERROR) << "Ehdr is required";
return false;
}
if (!elf_file) {
SB_LOG(ERROR) << "File is required";
return false;
}
phdr_num_ = elf_header->e_phnum;
SB_DLOG(INFO) << "Program Header count=" << phdr_num_;
// Like the kernel, only accept program header tables smaller than 64 KB.
if (phdr_num_ < 1 || phdr_num_ > 65536 / elf_header->e_phentsize) {
SB_LOG(ERROR) << "Invalid program header count: " << phdr_num_;
return false;
}
SB_DLOG(INFO) << "elf_header->e_phoff=" << elf_header->e_phoff;
SB_DLOG(INFO) << "elf_header->e_phnum=" << elf_header->e_phnum;
Addr page_min = PAGE_START(elf_header->e_phoff);
Addr page_max =
PAGE_END(elf_header->e_phoff + (phdr_num_ * elf_header->e_phentsize));
Addr page_offset = PAGE_OFFSET(elf_header->e_phoff);
SB_DLOG(INFO) << "page_min=" << page_min;
SB_DLOG(INFO) << "page_max=" << page_max;
phdr_size_ = page_max - page_min;
SB_DLOG(INFO) << "page_max - page_min=" << page_max - page_min;
if (memory_mapped_file_extension_) {
SB_LOG(INFO) << "Using memory mapped file for the program header";
phdr_mmap_ = memory_mapped_file_extension_->MemoryMapFile(
NULL, elf_file->GetName().c_str(), kSbMemoryMapProtectRead, page_min,
phdr_size_);
if (!phdr_mmap_) {
SB_LOG(ERROR) << "Failed to memory map the program header";
return false;
}
SB_DLOG(INFO) << "Allocated address=" << phdr_mmap_;
} else {
phdr_mmap_ =
mmap(nullptr, phdr_size_, PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (!phdr_mmap_) {
SB_LOG(ERROR) << "Failed to allocate memory";
return false;
}
SB_DLOG(INFO) << "Allocated address=" << phdr_mmap_;
if (!elf_file->ReadFromOffset(page_min, reinterpret_cast<char*>(phdr_mmap_),
phdr_size_)) {
SB_LOG(ERROR) << "Failed to read program header from file offset: "
<< page_min;
return false;
}
bool mp_result = mprotect(phdr_mmap_, phdr_size_, PROT_READ) == 0;
SB_DLOG(INFO) << "mp_result=" << mp_result;
if (!mp_result) {
SB_LOG(ERROR) << "Failed to protect program header";
return false;
}
}
phdr_table_ = reinterpret_cast<Phdr*>(reinterpret_cast<char*>(phdr_mmap_) +
page_offset);
SB_DLOG(INFO) << "phdr_table_=" << phdr_table_;
return true;
}
static bool ElfClassBuildIDNoteIdentifier(const void* section,
size_t length,
std::vector<uint8_t>& identifier) {
const void* section_end = reinterpret_cast<const char*>(section) + length;
const Nhdr* note_header = reinterpret_cast<const Nhdr*>(section);
while (reinterpret_cast<const void*>(note_header) < section_end) {
if (note_header->n_type == NT_GNU_BUILD_ID)
break;
note_header = reinterpret_cast<const Nhdr*>(
reinterpret_cast<const char*>(note_header) + sizeof(Nhdr) +
NOTE_PADDING(note_header->n_namesz) +
NOTE_PADDING(note_header->n_descsz));
}
if (reinterpret_cast<const void*>(note_header) >= section_end ||
note_header->n_descsz == 0) {
return false;
}
const uint8_t* build_id = reinterpret_cast<const uint8_t*>(note_header) +
sizeof(Nhdr) + NOTE_PADDING(note_header->n_namesz);
identifier.insert(identifier.end(), build_id,
build_id + note_header->n_descsz);
return true;
}
bool ProgramTable::LoadSegments(File* elf_file) {
for (size_t i = 0; i < phdr_num_; ++i) {
const Phdr* phdr = &phdr_table_[i];
if (phdr->p_type == PT_NOTE) {
if (!ElfClassBuildIDNoteIdentifier(
reinterpret_cast<const void*>(phdr->p_vaddr +
base_memory_address_),
phdr->p_memsz, build_id_)) {
SB_LOG(INFO) << "Could not get build id";
}
continue;
}
if (phdr->p_type != PT_LOAD) {
continue;
}
// Segment byte addresses in memory.
Addr seg_start = phdr->p_vaddr + base_memory_address_;
Addr seg_end = seg_start + phdr->p_memsz;
// Segment page addresses in memory.
Addr seg_page_start = PAGE_START(seg_start);
Addr seg_page_end = PAGE_END(seg_end);
// File offsets.
Addr seg_file_end = seg_start + phdr->p_filesz;
Addr file_start = phdr->p_offset;
Addr file_end = file_start + phdr->p_filesz;
SB_DLOG(INFO) << " phdr->p_offset=" << phdr->p_offset
<< " phdr->p_filesz=" << phdr->p_filesz;
Addr file_page_start = PAGE_START(file_start);
Addr file_length = file_end - file_page_start;
SB_DLOG(INFO) << "Mapping segment: " << " file_page_start="
<< file_page_start << " file_length=" << file_length
<< " seg_page_start=0x" << std::hex << seg_page_start;
if (file_length != 0) {
const int prot_flags = PFLAGS_TO_PROT(phdr->p_flags);
SB_DLOG(INFO) << "segment prot_flags=" << std::hex << prot_flags;
void* seg_addr = reinterpret_cast<void*>(seg_page_start);
bool mp_ret = false;
if (memory_mapped_file_extension_) {
SB_DLOG(INFO) << "Using Memory Mapped File for Loading the Segment";
void* p = memory_mapped_file_extension_->MemoryMapFile(
seg_addr, elf_file->GetName().c_str(), kSbMemoryMapProtectRead,
file_page_start, file_length);
if (!p) {
SB_LOG(ERROR) << "Failed to memory map file: " << elf_file->GetName();
return false;
}
} else {
SB_DLOG(INFO) << "Not using Memory Mapped Files";
mp_ret = mprotect(seg_addr, file_length, PROT_WRITE) == 0;
SB_DLOG(INFO) << "segment vaddress=" << seg_addr;
if (!mp_ret) {
SB_LOG(ERROR) << "Failed to unprotect segment";
return false;
}
if (!elf_file->ReadFromOffset(file_page_start,
reinterpret_cast<char*>(seg_addr),
file_length)) {
SB_DLOG(INFO) << "Failed to read segment from file offset: "
<< file_page_start;
return false;
}
}
mp_ret = mprotect(seg_addr, file_length, prot_flags) == 0;
SB_DLOG(INFO) << "mp_ret=" << mp_ret;
if (!mp_ret) {
SB_LOG(ERROR) << "Failed to protect segment";
return false;
}
if (!seg_addr) {
SB_LOG(ERROR) << "Could not map segment " << i;
return false;
}
}
// if the segment is writable, and does not end on a page boundary,
// zero-fill it until the page limit.
if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
memset(reinterpret_cast<void*>(seg_file_end), 0,
PAGE_SIZE - PAGE_OFFSET(seg_file_end));
}
seg_file_end = PAGE_END(seg_file_end);
// seg_file_end is now the first page address after the file
// content. If seg_page_end is larger, we need to zero anything
// between them. This is done by using a private anonymous
// map for all extra pages.
if (seg_page_end > seg_file_end) {
bool mprotect_fix =
mprotect(reinterpret_cast<void*>(seg_file_end),
seg_page_end - seg_file_end, PROT_WRITE) == 0;
SB_DLOG(INFO) << "mprotect_fix=" << mprotect_fix;
if (!mprotect_fix) {
SB_LOG(ERROR) << "Failed to unprotect end of segment";
return false;
}
memset(reinterpret_cast<void*>(seg_file_end), 0,
seg_page_end - seg_file_end);
mprotect(reinterpret_cast<void*>(seg_file_end),
seg_page_end - seg_file_end, PFLAGS_TO_PROT(phdr->p_flags));
SB_DLOG(INFO) << "mprotect_fix=" << mprotect_fix;
if (!mprotect_fix) {
SB_LOG(ERROR) << "Failed to protect end of segment";
return false;
}
}
}
return true;
}
size_t ProgramTable::GetLoadMemorySize() {
Addr min_vaddr = ~static_cast<Addr>(0);
Addr max_vaddr = 0x00000000U;
bool found_pt_load = false;
for (size_t i = 0; i < phdr_num_; ++i) {
const Phdr* phdr = &phdr_table_[i];
if (phdr->p_type != PT_LOAD) {
SB_DLOG(INFO) << "GetLoadMemorySize: ignoring segment with type: "
<< phdr->p_type;
continue;
}
found_pt_load = true;
if (phdr->p_vaddr < min_vaddr) {
SB_DLOG(INFO) << "p_vaddr=" << std::hex << phdr->p_vaddr;
min_vaddr = phdr->p_vaddr;
}
if (phdr->p_vaddr + phdr->p_memsz > max_vaddr) {
max_vaddr = phdr->p_vaddr + phdr->p_memsz;
SB_DLOG(INFO) << "phdr->p_vaddr=" << phdr->p_vaddr
<< " phdr->p_memsz=" << phdr->p_memsz;
SB_DLOG(INFO) << " max_vaddr=0x" << std::hex << max_vaddr;
}
}
if (!found_pt_load) {
min_vaddr = 0x00000000U;
}
min_vaddr = PAGE_START(min_vaddr);
max_vaddr = PAGE_END(max_vaddr);
return max_vaddr - min_vaddr;
}
void ProgramTable::GetDynamicSection(Dyn** dynamic,
size_t* dynamic_count,
Word* dynamic_flags) {
const Phdr* phdr = phdr_table_;
const Phdr* phdr_limit = phdr + phdr_num_;
for (phdr = phdr_table_; phdr < phdr_limit; phdr++) {
if (phdr->p_type != PT_DYNAMIC) {
SB_DLOG(INFO) << "Ignore section with type: " << phdr->p_type;
continue;
}
SB_DLOG(INFO) << "Reading at vaddr: " << phdr->p_vaddr;
*dynamic = reinterpret_cast<Dyn*>(base_memory_address_ + phdr->p_vaddr);
if (dynamic_count) {
*dynamic_count = static_cast<size_t>((phdr->p_memsz / sizeof(Dyn)));
}
if (dynamic_flags) {
*dynamic_flags = phdr->p_flags;
}
return;
}
*dynamic = NULL;
if (dynamic_count) {
*dynamic_count = 0;
}
}
int ProgramTable::AdjustMemoryProtectionOfReadOnlySegments(
int extra_prot_flags) {
const Phdr* phdr = phdr_table_;
const Phdr* phdr_limit = phdr + phdr_num_;
for (; phdr < phdr_limit; phdr++) {
if (phdr->p_type != PT_LOAD || (phdr->p_flags & PF_W) != 0)
continue;
Addr seg_page_start = PAGE_START(phdr->p_vaddr) + base_memory_address_;
Addr seg_page_end =
PAGE_END(phdr->p_vaddr + phdr->p_memsz) + base_memory_address_;
int ret = mprotect(reinterpret_cast<void*>(seg_page_start),
seg_page_end - seg_page_start,
PFLAGS_TO_PROT(phdr->p_flags) | extra_prot_flags);
if (ret < 0) {
return -1;
}
}
return 0;
}
bool ProgramTable::ReserveLoadMemory() {
load_size_ = GetLoadMemorySize();
if (load_size_ == 0) {
SB_LOG(ERROR) << "No loadable segments";
return false;
}
SB_DLOG(INFO) << "Load size=" << load_size_;
load_start_ =
mmap(nullptr, load_size_, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
if (load_start_ == MAP_FAILED) {
SB_LOG(ERROR) << "Could not reserve " << load_size_
<< " bytes of address space";
return false;
}
base_memory_address_ = reinterpret_cast<Addr>(load_start_);
SB_LOG(INFO) << "Load start=" << std::hex << load_start_
<< " base_memory_address=0x" << base_memory_address_;
return true;
}
void ProgramTable::PublishEvergreenInfo(const char* file_path) {
EvergreenInfo evergreen_info;
memset(&evergreen_info, 0, sizeof(EvergreenInfo));
starboard::strlcpy(evergreen_info.file_path_buf, file_path,
EVERGREEN_FILE_PATH_MAX_SIZE);
evergreen_info.base_address = base_memory_address_;
evergreen_info.load_size = load_size_;
evergreen_info.phdr_table = (uint64_t)phdr_table_;
evergreen_info.phdr_table_num = phdr_num_;
std::vector<char> tmp(build_id_.begin(), build_id_.end());
tmp.push_back('\0');
starboard::strlcpy(evergreen_info.build_id, tmp.data(),
EVERGREEN_BUILD_ID_MAX_SIZE);
evergreen_info.build_id_length = build_id_.size();
SetEvergreenInfo(&evergreen_info);
}
Addr ProgramTable::GetBaseMemoryAddress() {
return base_memory_address_;
}
ProgramTable::~ProgramTable() {
SetEvergreenInfo(NULL);
if (load_start_) {
munmap(load_start_, load_size_);
}
if (phdr_mmap_) {
munmap(phdr_mmap_, phdr_size_);
}
}
} // namespace elf_loader
} // namespace starboard