Chromium Code Reviews| 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..1844ee5f7c01a014a985c90e6d0f3012f3ad8d8c |
| --- /dev/null |
| +++ b/src/nonsfi/loader/elf_loader.c |
| @@ -0,0 +1,196 @@ |
| +/* |
| + * 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" |
| + |
| + |
| +#define PAGE_SIZE 0x1000 |
|
bradn
2014/05/02 17:39:11
There's an odd irony in baking this in given it wo
Mark Seaborn
2014/05/02 18:18:50
Since you mention it, I've added a comment.
|
| +#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 i = 0; |
|
bradn
2014/05/02 17:39:11
Given you're using i beyond the loop, maybe a more
Mark Seaborn
2014/05/02 18:18:50
Done.
|
| + while (i < ehdr.e_phnum && phdr[i].p_type != PT_LOAD) |
| + ++i; |
| + if (i == 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[i]; |
| + 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; |
| + |
| + uintptr_t prev_segment_end = 0; |
| + ElfW(Phdr) *ph; |
| + for (ph = first_load; ph <= last_load; ++ph) { |
| + if (ph->p_type == PT_LOAD) { |
|
bradn
2014/05/02 17:39:11
might be more readable with continue, to avoid the
Mark Seaborn
2014/05/02 18:18:50
Done.
|
| + 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"); |
| + } |
| + |
| + /* 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"); |
| + } |
| + |
| + return load_bias + ehdr.e_entry; |
|
bradn
2014/05/02 17:39:11
Check it's in one of the sections?
Mark Seaborn
2014/05/02 18:18:50
OK. I've even checked that it's in an executable
|
| +} |
| + |
| +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); |
| +} |