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); |
+} |