| Index: src/nonsfi/loader/elf_loader.c
|
| diff --git a/src/nonsfi/loader/elf_loader.c b/src/nonsfi/loader/elf_loader.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8f68a1465bc5d07d18e03526fc19e353d72ffa1d
|
| --- /dev/null
|
| +++ b/src/nonsfi/loader/elf_loader.c
|
| @@ -0,0 +1,213 @@
|
| +/*
|
| + * Copyright (c) 2014 The Native Client 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 <elf.h>
|
| +#include <errno.h>
|
| +#include <fcntl.h>
|
| +#include <link.h>
|
| +#include <stdint.h>
|
| +#include <stdio.h>
|
| +#include <stdlib.h>
|
| +#include <string.h>
|
| +#include <sys/mman.h>
|
| +#include <unistd.h>
|
| +
|
| +#include "native_client/src/nonsfi/irt/irt_interfaces.h"
|
| +#include "native_client/src/shared/platform/nacl_log.h"
|
| +#include "native_client/src/trusted/service_runtime/nacl_config.h"
|
| +
|
| +
|
| +/*
|
| + * Note that Non-SFI NaCl uses a 4k page size (in order to allow loading
|
| + * existing Linux DSOs), in contrast to SFI NaCl's 64k page size (required
|
| + * for running on Windows).
|
| + */
|
| +#define PAGE_SIZE 0x1000
|
| +#define PAGE_MASK (PAGE_SIZE - 1)
|
| +#define MAX_PHNUM 128
|
| +
|
| +static uintptr_t PageSizeRoundDown(uintptr_t addr) {
|
| + return addr & ~PAGE_MASK;
|
| +}
|
| +
|
| +static uintptr_t PageSizeRoundUp(uintptr_t addr) {
|
| + return PageSizeRoundDown(addr + PAGE_SIZE - 1);
|
| +}
|
| +
|
| +static int ElfFlagsToMmapFlags(int pflags) {
|
| + return ((pflags & PF_X) != 0 ? PROT_EXEC : 0) |
|
| + ((pflags & PF_R) != 0 ? PROT_READ : 0) |
|
| + ((pflags & PF_W) != 0 ? PROT_WRITE : 0);
|
| +}
|
| +
|
| +static void CheckElfHeaders(ElfW(Ehdr) *ehdr) {
|
| + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) {
|
| + NaClLog(LOG_FATAL, "Not an ELF file: no ELF header\n");
|
| + }
|
| + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32) {
|
| + NaClLog(LOG_FATAL, "Unexpected ELF class: not ELFCLASS32\n");
|
| + }
|
| + if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) {
|
| + NaClLog(LOG_FATAL, "Not a little-endian ELF file\n");
|
| + }
|
| + if (ehdr->e_type != ET_DYN) {
|
| + NaClLog(LOG_FATAL, "Not a relocatable ELF object (not ET_DYN)\n");
|
| + }
|
| + if (ehdr->e_machine != NACL_ELF_E_MACHINE) {
|
| + NaClLog(LOG_FATAL, "Unexpected ELF e_machine field\n");
|
| + }
|
| + if (ehdr->e_version != EV_CURRENT) {
|
| + NaClLog(LOG_FATAL, "Unexpected ELF e_version field\n");
|
| + }
|
| + if (ehdr->e_ehsize != sizeof(*ehdr)) {
|
| + NaClLog(LOG_FATAL, "Unexpected ELF e_ehsize field\n");
|
| + }
|
| + if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) {
|
| + NaClLog(LOG_FATAL, "Unexpected ELF e_phentsize field\n");
|
| + }
|
| +}
|
| +
|
| +static uintptr_t LoadElfFile(const char *filename) {
|
| + int fd = open(filename, O_RDONLY);
|
| + if (fd < 0) {
|
| + NaClLog(LOG_FATAL, "Failed to open %s: %s\n", filename, strerror(errno));
|
| + }
|
| +
|
| + /* Read ELF file headers. */
|
| + ElfW(Ehdr) ehdr;
|
| + ssize_t bytes_read = pread(fd, &ehdr, sizeof(ehdr), 0);
|
| + if (bytes_read != sizeof(ehdr)) {
|
| + NaClLog(LOG_FATAL, "Failed to read ELF file headers\n");
|
| + }
|
| + CheckElfHeaders(&ehdr);
|
| +
|
| + /* Read ELF program headers. */
|
| + if (ehdr.e_phnum > MAX_PHNUM) {
|
| + NaClLog(LOG_FATAL, "ELF file has too many program headers\n");
|
| + }
|
| + ElfW(Phdr) phdr[MAX_PHNUM];
|
| + ssize_t phdrs_size = sizeof(phdr[0]) * ehdr.e_phnum;
|
| + bytes_read = pread(fd, phdr, phdrs_size, ehdr.e_phoff);
|
| + if (bytes_read != phdrs_size) {
|
| + NaClLog(LOG_FATAL, "Failed to read ELF program headers\n");
|
| + }
|
| +
|
| + /* Find the first PT_LOAD segment. */
|
| + size_t phdr_index = 0;
|
| + while (phdr_index < ehdr.e_phnum && phdr[phdr_index].p_type != PT_LOAD)
|
| + ++phdr_index;
|
| + if (phdr_index == ehdr.e_phnum) {
|
| + NaClLog(LOG_FATAL, "ELF file has no PT_LOAD header\n");
|
| + }
|
| +
|
| + /*
|
| + * ELF requires that PT_LOAD segments be in ascending order of p_vaddr.
|
| + * Find the last one to calculate the whole address span of the image.
|
| + */
|
| + ElfW(Phdr) *first_load = &phdr[phdr_index];
|
| + ElfW(Phdr) *last_load = &phdr[ehdr.e_phnum - 1];
|
| + while (last_load > first_load && last_load->p_type != PT_LOAD)
|
| + --last_load;
|
| +
|
| + if (first_load->p_vaddr != 0) {
|
| + NaClLog(LOG_FATAL, "First PT_LOAD segment's load address is not 0\n");
|
| + }
|
| + size_t span = last_load->p_vaddr + last_load->p_memsz;
|
| +
|
| + /* Reserve address space. */
|
| + void *mapping = mmap(NULL, span, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE,
|
| + -1, 0);
|
| + if (mapping == MAP_FAILED) {
|
| + NaClLog(LOG_FATAL, "Failed to reserve address space for executable\n");
|
| + }
|
| + uintptr_t load_bias = (uintptr_t) mapping;
|
| +
|
| + /* Map the PT_LOAD segments. */
|
| + uintptr_t prev_segment_end = 0;
|
| + int entry_point_is_valid = 0;
|
| + ElfW(Phdr) *ph;
|
| + for (ph = first_load; ph <= last_load; ++ph) {
|
| + if (ph->p_type != PT_LOAD)
|
| + continue;
|
| + int prot = ElfFlagsToMmapFlags(ph->p_flags);
|
| + uintptr_t segment_start = PageSizeRoundDown(ph->p_vaddr);
|
| + uintptr_t segment_end = PageSizeRoundUp(ph->p_vaddr + ph->p_memsz);
|
| + if (segment_start < prev_segment_end) {
|
| + NaClLog(LOG_FATAL, "PT_LOAD segments overlap or are not sorted\n");
|
| + }
|
| + prev_segment_end = segment_end;
|
| + void *segment_addr = (void *) (load_bias + segment_start);
|
| + void *map_result = mmap((void *) segment_addr,
|
| + segment_end - segment_start,
|
| + prot, MAP_PRIVATE | MAP_FIXED, fd,
|
| + PageSizeRoundDown(ph->p_offset));
|
| + if (map_result != segment_addr) {
|
| + NaClLog(LOG_FATAL, "Failed to map ELF segment\n");
|
| + }
|
| +
|
| + if ((ph->p_flags & PF_X) != 0 &&
|
| + ph->p_vaddr <= ehdr.e_entry &&
|
| + ehdr.e_entry < ph->p_vaddr + ph->p_filesz) {
|
| + entry_point_is_valid = 1;
|
| + }
|
| +
|
| + /* Handle the BSS. */
|
| + if (ph->p_memsz < ph->p_filesz) {
|
| + NaClLog(LOG_FATAL, "Bad ELF segment: p_memsz < p_filesz\n");
|
| + }
|
| + if (ph->p_memsz > ph->p_filesz) {
|
| + if ((ph->p_flags & PF_W) == 0) {
|
| + NaClLog(LOG_FATAL,
|
| + "Bad ELF segment: non-writable segment with BSS\n");
|
| + }
|
| +
|
| + uintptr_t bss_start = ph->p_vaddr + ph->p_filesz;
|
| + uintptr_t bss_map_start = PageSizeRoundUp(bss_start);
|
| + /*
|
| + * Zero the BSS to the end of the page.
|
| + *
|
| + * Zeroing beyond p_memsz might be more than is necessary for Non-SFI
|
| + * NaCl. On Linux, programs such as ld.so use the rest of the page,
|
| + * after p_memsz, as part of the brk() heap and assume that it has
|
| + * been zeroed. Non-SFI NaCl does not provide a brk() heap, though.
|
| + * However, zeroing to the end of the page is simple enough, and it's
|
| + * consistent with the case in additional pages must be mapped, which
|
| + * will all be fully zeroed.
|
| + */
|
| + memset((void *) (load_bias + bss_start), 0, bss_map_start - bss_start);
|
| +
|
| + if (bss_map_start < segment_end) {
|
| + void *map_addr = (void *) (load_bias + bss_map_start);
|
| + map_result = mmap(map_addr, segment_end - bss_map_start,
|
| + prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
|
| + -1, 0);
|
| + if (map_result != map_addr) {
|
| + NaClLog(LOG_FATAL, "Failed to map BSS for ELF segment\n");
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (close(fd) != 0) {
|
| + NaClLog(LOG_FATAL, "close() failed\n");
|
| + }
|
| +
|
| + if (!entry_point_is_valid) {
|
| + NaClLog(LOG_FATAL, "ELF entry point does not point into an executable "
|
| + "PT_LOAD segment\n");
|
| + }
|
| + return load_bias + ehdr.e_entry;
|
| +}
|
| +
|
| +int main(int argc, char **argv, char **environ) {
|
| + if (argc < 2) {
|
| + fprintf(stderr, "Usage: %s <executable> <args...>\n", argv[0]);
|
| + return 1;
|
| + }
|
| + const char *nexe_filename = argv[1];
|
| + uintptr_t entry = LoadElfFile(nexe_filename);
|
| + return nacl_irt_nonsfi_entry(argc, argv, environ, (nacl_entry_func_t) entry);
|
| +}
|
|
|