blob: 28a9441761f522f0e705ddc66027c278c51e73dd [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <elfload/elfload.h>
#include <endian.h>
#include <limits.h>
#include <string.h>
#include <zircon/syscalls.h>
#if BYTE_ORDER == LITTLE_ENDIAN
# define MY_ELFDATA ELFDATA2LSB
#elif BYTE_ORDER == BIG_ENDIAN
# define MY_ELFDATA ELFDATA2MSB
#else
# error what byte order?
#endif
#if defined(__arm__)
# define MY_MACHINE EM_ARM
#elif defined(__aarch64__)
# define MY_MACHINE EM_AARCH64
#elif defined(__x86_64__)
# define MY_MACHINE EM_X86_64
#elif defined(__i386__)
# define MY_MACHINE EM_386
#else
# error what machine?
#endif
#define VMO_NAME_UNKNOWN "<unknown ELF file>"
#define VMO_NAME_PREFIX_BSS "bss:"
#define VMO_NAME_PREFIX_DATA "data:"
// NOTE! All code in this file must maintain the invariants that it's
// purely position-independent and uses no writable memory other than
// its own stack.
// hdr_buf represents bytes already read from the start of the file.
zx_status_t elf_load_prepare(zx_handle_t vmo, const void* hdr_buf, size_t buf_sz,
elf_load_header_t* header, uintptr_t* phoff) {
// Read the file header and validate basic format sanity.
elf_ehdr_t ehdr;
if (buf_sz >= sizeof(ehdr)) {
memcpy(&ehdr, hdr_buf, sizeof(ehdr));
} else {
zx_status_t status = zx_vmo_read(vmo, &ehdr, 0, sizeof(ehdr));
if (status != ZX_OK)
return status;
}
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 ||
ehdr.e_ident[EI_MAG1] != ELFMAG1 ||
ehdr.e_ident[EI_MAG2] != ELFMAG2 ||
ehdr.e_ident[EI_MAG3] != ELFMAG3 ||
ehdr.e_ident[EI_CLASS] != MY_ELFCLASS ||
ehdr.e_ident[EI_DATA] != MY_ELFDATA ||
ehdr.e_ident[EI_VERSION] != EV_CURRENT ||
ehdr.e_phentsize != sizeof(elf_phdr_t) ||
ehdr.e_phnum == PN_XNUM ||
ehdr.e_machine != MY_MACHINE ||
// This code could easily support loading fixed-address ELF files
// (e_type == ET_EXEC). But the system overall doesn't support
// them. It's Fuchsia policy that all executables must be PIEs.
// So don't accept ET_EXEC files at all.
ehdr.e_type != ET_DYN)
return ERR_ELF_BAD_FORMAT;
// Cache the few other bits we need from the header, and we're good to go.
header->e_phnum = ehdr.e_phnum;
header->e_entry = ehdr.e_entry;
*phoff = ehdr.e_phoff;
return ZX_OK;
}
zx_status_t elf_load_read_phdrs(zx_handle_t vmo, elf_phdr_t phdrs[],
uintptr_t phoff, size_t phnum) {
size_t phdrs_size = (size_t)phnum * sizeof(elf_phdr_t);
return zx_vmo_read(vmo, phdrs, phoff, phdrs_size);
}
// An ET_DYN file can be loaded anywhere, so choose where. This
// allocates a VMAR to hold the image, and returns its handle and
// absolute address. This also computes the "load bias", which is the
// difference between p_vaddr values in this file and actual runtime
// addresses. (Usually the lowest p_vaddr in an ET_DYN file will be 0
// and so the load bias is also the load base address, but ELF does
// not require that the lowest p_vaddr be 0.)
static zx_status_t choose_load_bias(zx_handle_t root_vmar,
const elf_load_header_t* header,
const elf_phdr_t phdrs[],
zx_handle_t* vmar,
uintptr_t* vmar_base,
uintptr_t* bias) {
// This file can be loaded anywhere, so the first thing is to
// figure out the total span it will need and reserve a span
// of address space that big. The kernel decides where to put it.
bool first = true;
uintptr_t low = 0, high = 0;
uint32_t max_perm = 0;
for (uint_fast16_t i = 0; i < header->e_phnum; ++i) {
if (phdrs[i].p_type == PT_LOAD) {
uintptr_t start = phdrs[i].p_vaddr & -PAGE_SIZE;
uintptr_t end = ((phdrs[i].p_vaddr +
phdrs[i].p_memsz + PAGE_SIZE - 1) & -PAGE_SIZE);
// Sanity check. ELF requires that PT_LOAD phdrs be sorted in
// ascending p_vaddr order and not overlap.
if (first) {
low = start;
first = false;
} else if (start < high) {
return ERR_ELF_BAD_FORMAT;
}
high = end;
max_perm |= phdrs[i].p_flags;
}
}
const size_t span = high - low;
if (span == 0)
return ZX_OK;
// Allocate a VMAR to reserve the whole address range.
zx_status_t status = zx_vmar_allocate(
root_vmar,
((max_perm & PF_R) ? ZX_VM_CAN_MAP_READ : 0) |
((max_perm & PF_W) ? ZX_VM_CAN_MAP_WRITE : 0) |
((max_perm & PF_X) ? ZX_VM_CAN_MAP_EXECUTE : 0) |
ZX_VM_CAN_MAP_SPECIFIC,
0, span, vmar, vmar_base);
if (status == ZX_OK)
*bias = *vmar_base - low;
return status;
}
static zx_status_t finish_load_segment(
zx_handle_t vmar, zx_handle_t vmo, const char vmo_name[ZX_MAX_NAME_LEN],
const elf_phdr_t* ph, size_t start_offset, size_t size,
uintptr_t file_start, uintptr_t file_end, size_t partial_page) {
const zx_vm_option_t options = ZX_VM_SPECIFIC |
((ph->p_flags & PF_R) ? ZX_VM_PERM_READ : 0) |
((ph->p_flags & PF_W) ? ZX_VM_PERM_WRITE : 0) |
((ph->p_flags & PF_X) ? ZX_VM_PERM_EXECUTE : 0);
uintptr_t start;
if (ph->p_filesz == ph->p_memsz)
// Straightforward segment, map all the whole pages from the file.
return zx_vmar_map(vmar, options, start_offset, vmo, file_start, size,
&start);
const size_t file_size = file_end - file_start;
// This segment has some bss, so things are more complicated.
// Only the leading portion is directly mapped in from the file.
if (file_size > 0) {
zx_status_t status = zx_vmar_map(vmar, options, start_offset, vmo,
file_start, file_size, &start);
if (status != ZX_OK)
return status;
start_offset += file_size;
size -= file_size;
}
// The rest of the segment will be backed by anonymous memory.
zx_handle_t bss_vmo;
zx_status_t status = zx_vmo_create(size, 0, &bss_vmo);
if (status != ZX_OK)
return status;
char bss_vmo_name[ZX_MAX_NAME_LEN] = VMO_NAME_PREFIX_BSS;
memcpy(&bss_vmo_name[sizeof(VMO_NAME_PREFIX_BSS) - 1],
vmo_name, ZX_MAX_NAME_LEN - sizeof(VMO_NAME_PREFIX_BSS));
status = zx_object_set_property(bss_vmo, ZX_PROP_NAME,
bss_vmo_name, strlen(bss_vmo_name));
if (status != ZX_OK) {
zx_handle_close(bss_vmo);
return status;
}
// The final partial page of initialized data falls into the
// region backed by bss_vmo rather than (the file) vmo. We need
// to read that data out of the file and copy it into bss_vmo.
if (partial_page > 0) {
char buffer[PAGE_SIZE];
status = zx_vmo_read(vmo, buffer, file_end, partial_page);
if (status != ZX_OK) {
zx_handle_close(bss_vmo);
return status;
}
status = zx_vmo_write(bss_vmo, buffer, 0, partial_page);
if (status != ZX_OK) {
zx_handle_close(bss_vmo);
return status;
}
}
status = zx_vmar_map(vmar, options, start_offset, bss_vmo, 0, size, &start);
zx_handle_close(bss_vmo);
return status;
}
static zx_status_t load_segment(zx_handle_t vmar, size_t vmar_offset,
zx_handle_t vmo, const char* vmo_name,
const elf_phdr_t* ph) {
// The p_vaddr can start in the middle of a page, but the
// semantics are that all the whole pages containing the
// p_vaddr+p_filesz range are mapped in.
size_t start = (size_t)ph->p_vaddr + vmar_offset;
size_t end = start + ph->p_memsz;
start &= -PAGE_SIZE;
end = (end + PAGE_SIZE - 1) & -PAGE_SIZE;
size_t size = end - start;
// Nothing to do for an empty segment (degenerate case).
if (size == 0)
return ZX_OK;
uintptr_t file_start = (uintptr_t)ph->p_offset;
uintptr_t file_end = file_start + ph->p_filesz;
const size_t partial_page = file_end & (PAGE_SIZE - 1);
file_start &= -PAGE_SIZE;
file_end &= -PAGE_SIZE;
uintptr_t data_end =
(ph->p_offset + ph->p_filesz + PAGE_SIZE - 1) & -PAGE_SIZE;
const size_t data_size = data_end - file_start;
// With no writable data, it's the simple case.
if (!(ph->p_flags & PF_W) || data_size == 0)
return finish_load_segment(vmar, vmo, vmo_name, ph, start, size,
file_start, file_end, partial_page);
// For a writable segment, we need a writable VMO.
zx_handle_t writable_vmo;
zx_status_t status = zx_vmo_create_child(vmo, ZX_VMO_CHILD_COPY_ON_WRITE,
file_start, data_size, &writable_vmo);
if (status == ZX_OK) {
char name[ZX_MAX_NAME_LEN] = VMO_NAME_PREFIX_DATA;
memcpy(&name[sizeof(VMO_NAME_PREFIX_DATA) - 1],
vmo_name, ZX_MAX_NAME_LEN - sizeof(VMO_NAME_PREFIX_DATA));
status = zx_object_set_property(writable_vmo, ZX_PROP_NAME,
name, strlen(name));
if (status == ZX_OK)
status = finish_load_segment(
vmar, writable_vmo, vmo_name, ph, start, size,
0, file_end - file_start, partial_page);
zx_handle_close(writable_vmo);
}
return status;
}
zx_status_t elf_load_map_segments(zx_handle_t root_vmar,
const elf_load_header_t* header,
const elf_phdr_t phdrs[],
zx_handle_t vmo,
zx_handle_t* segments_vmar,
zx_vaddr_t* base, zx_vaddr_t* entry) {
char vmo_name[ZX_MAX_NAME_LEN];
if (zx_object_get_property(vmo, ZX_PROP_NAME,
vmo_name, sizeof(vmo_name)) != ZX_OK ||
vmo_name[0] == '\0')
memcpy(vmo_name, VMO_NAME_UNKNOWN, sizeof(VMO_NAME_UNKNOWN));
uintptr_t vmar_base = 0;
uintptr_t bias = 0;
zx_handle_t vmar = ZX_HANDLE_INVALID;
zx_status_t status = choose_load_bias(root_vmar, header, phdrs,
&vmar, &vmar_base, &bias);
size_t vmar_offset = bias - vmar_base;
for (uint_fast16_t i = 0; status == ZX_OK && i < header->e_phnum; ++i) {
if (phdrs[i].p_type == PT_LOAD)
status = load_segment(vmar, vmar_offset, vmo, vmo_name, &phdrs[i]);
}
if (status == ZX_OK && segments_vmar != NULL)
*segments_vmar = vmar;
else
zx_handle_close(vmar);
if (status == ZX_OK) {
if (base != NULL)
*base = vmar_base;
if (entry != NULL)
*entry = header->e_entry != 0 ? header->e_entry + bias : 0;
}
return status;
}
bool elf_load_find_interp(const elf_phdr_t phdrs[], size_t phnum,
uintptr_t* interp_off, size_t* interp_len) {
for (size_t i = 0; i < phnum; ++i) {
if (phdrs[i].p_type == PT_INTERP) {
*interp_off = phdrs[i].p_offset;
*interp_len = phdrs[i].p_filesz;
return true;
}
}
return false;
}