| Index: chrome/nacl/nacl_helper_bootstrap_linux.c
|
| diff --git a/chrome/nacl/nacl_helper_bootstrap_linux.c b/chrome/nacl/nacl_helper_bootstrap_linux.c
|
| index 7a0ace7ae2fc51fd6d6dd634e1236505e0aa9fa9..c534c395d94f8b01f01d05187ac911b355b617e8 100644
|
| --- a/chrome/nacl/nacl_helper_bootstrap_linux.c
|
| +++ b/chrome/nacl/nacl_helper_bootstrap_linux.c
|
| @@ -2,23 +2,428 @@
|
| * Use of this source code is governed by a BSD-style license that can be
|
| * found in the LICENSE file.
|
| *
|
| - * Bootstraping the nacl_helper. This executable reserves the bottom 1G
|
| - * of the address space, then invokes nacl_helper_init. Note that,
|
| - * as the text of this executable will eventually be overwritten by the
|
| - * native_client module, nacl_helper_init must not attempt to return.
|
| + * This is a standalone program that loads and runs the dynamic linker.
|
| + * This program itself must be linked statically. To keep it small, it's
|
| + * written to avoid all dependencies on libc and standard startup code.
|
| + * Hence, this should be linked using -nostartfiles. It must be compiled
|
| + * with -fno-stack-protector to ensure the compiler won't emit code that
|
| + * presumes some special setup has been done.
|
| + *
|
| + * On ARM, the compiler will emit calls to some libc functions, so we
|
| + * cannot link with -nostdlib. The functions it does use (memset and
|
| + * __aeabi_* functions for integer division) are sufficiently small and
|
| + * self-contained in ARM's libc.a that we don't have any problem using
|
| + * the libc definitions though we aren't using the rest of libc or doing
|
| + * any of the setup it might expect.
|
| + */
|
| +
|
| +#include <elf.h>
|
| +#include <fcntl.h>
|
| +#include <link.h>
|
| +#include <stddef.h>
|
| +#include <stdint.h>
|
| +#include <sys/mman.h>
|
| +
|
| +#define MAX_PHNUM 12
|
| +
|
| +#if defined(__i386__)
|
| +# define DYNAMIC_LINKER "/lib/ld-linux.so.2"
|
| +#elif defined(__x86_64__)
|
| +# define DYNAMIC_LINKER "/lib64/ld-linux-x86-64.so.2"
|
| +#elif defined(__ARM_EABI__)
|
| +# define DYNAMIC_LINKER "/lib/ld-linux.so.3"
|
| +#else
|
| +# error "Don't know the dynamic linker file name for this architecture!"
|
| +#endif
|
| +
|
| +
|
| +/*
|
| + * We're not using <string.h> functions here, to avoid dependencies.
|
| + * In the x86 libc, even "simple" functions like memset and strlen can
|
| + * depend on complex startup code, because in newer libc
|
| + * implementations they are defined using STT_GNU_IFUNC.
|
| + */
|
| +
|
| +static void my_bzero(void *buf, size_t n) {
|
| + char *p = buf;
|
| + while (n-- > 0)
|
| + *p++ = 0;
|
| +}
|
| +
|
| +static size_t my_strlen(const char *s) {
|
| + size_t n = 0;
|
| + while (*s++ != '\0')
|
| + ++n;
|
| + return n;
|
| +}
|
| +
|
| +
|
| +/*
|
| + * Get inline functions for system calls.
|
| + */
|
| +static int my_errno;
|
| +#define SYS_ERRNO my_errno
|
| +#include "third_party/lss/linux_syscall_support.h"
|
| +
|
| +
|
| +/*
|
| + * We're avoiding libc, so no printf. The only nontrivial thing we need
|
| + * is rendering numbers, which is, in fact, pretty trivial.
|
| + */
|
| +static void iov_int_string(int value, struct kernel_iovec *iov,
|
| + char *buf, size_t bufsz) {
|
| + char *p = &buf[bufsz];
|
| + do {
|
| + --p;
|
| + *p = "0123456789"[value % 10];
|
| + value /= 10;
|
| + } while (value != 0);
|
| + iov->iov_base = p;
|
| + iov->iov_len = &buf[bufsz] - p;
|
| +}
|
| +
|
| +#define STRING_IOV(string_constant, cond) \
|
| + { (void *) string_constant, cond ? (sizeof(string_constant) - 1) : 0 }
|
| +
|
| +__attribute__((noreturn)) static void fail(const char *message,
|
| + const char *item1, int value1,
|
| + const char *item2, int value2) {
|
| + char valbuf1[32];
|
| + char valbuf2[32];
|
| + struct kernel_iovec iov[] = {
|
| + STRING_IOV("bootstrap_helper", 1),
|
| + STRING_IOV(DYNAMIC_LINKER, 1),
|
| + STRING_IOV(": ", 1),
|
| + { (void *) message, my_strlen(message) },
|
| + { (void *) item1, item1 == NULL ? 0 : my_strlen(item1) },
|
| + STRING_IOV("=", item1 != NULL),
|
| + {},
|
| + STRING_IOV(", ", item1 != NULL && item2 != NULL),
|
| + { (void *) item2, item2 == NULL ? 0 : my_strlen(item2) },
|
| + STRING_IOV("=", item2 != NULL),
|
| + {},
|
| + { "\n", 1 },
|
| + };
|
| + const int niov = sizeof(iov) / sizeof(iov[0]);
|
| +
|
| + if (item1 != NULL)
|
| + iov_int_string(value1, &iov[6], valbuf1, sizeof(valbuf1));
|
| + if (item2 != NULL)
|
| + iov_int_string(value1, &iov[10], valbuf2, sizeof(valbuf2));
|
| +
|
| + sys_writev(2, iov, niov);
|
| + sys_exit_group(2);
|
| + while (1) *(volatile int *) 0 = 0; /* Crash. */
|
| +}
|
| +
|
| +
|
| +static int my_open(const char *file, int oflag) {
|
| + int result = sys_open(file, oflag, 0);
|
| + if (result < 0)
|
| + fail("Cannot open dynamic linker! ", "errno", my_errno, NULL, 0);
|
| + return result;
|
| +}
|
| +
|
| +static void my_pread(const char *fail_message,
|
| + int fd, void *buf, size_t bufsz, uintptr_t pos) {
|
| + ssize_t result = sys_pread64(fd, buf, bufsz, pos);
|
| + if (result < 0)
|
| + fail(fail_message, "errno", my_errno, NULL, 0);
|
| + if ((size_t) result != bufsz)
|
| + fail(fail_message, "read count", result, NULL, 0);
|
| +}
|
| +
|
| +static uintptr_t my_mmap(const char *segment_type, unsigned int segnum,
|
| + uintptr_t address, size_t size,
|
| + int prot, int flags, int fd, uintptr_t pos) {
|
| +#if defined(__NR_mmap2)
|
| + void *result = sys_mmap2((void *) address, size, prot, flags, fd, pos >> 12);
|
| +#else
|
| + void *result = sys_mmap((void *) address, size, prot, flags, fd, pos);
|
| +#endif
|
| + if (result == MAP_FAILED)
|
| + fail("Failed to map from dynamic linker! ",
|
| + segment_type, segnum, "errno", my_errno);
|
| + return (uintptr_t) result;
|
| +}
|
| +
|
| +static void my_mprotect(unsigned int segnum,
|
| + uintptr_t address, size_t size, int prot) {
|
| + if (sys_mprotect((void *) address, size, prot) < 0)
|
| + fail("Failed to mprotect hole in dynamic linker! ",
|
| + "segment", segnum, "errno", my_errno);
|
| +}
|
| +
|
| +
|
| +static int prot_from_phdr(const ElfW(Phdr) *phdr) {
|
| + int prot = 0;
|
| + if (phdr->p_flags & PF_R)
|
| + prot |= PROT_READ;
|
| + if (phdr->p_flags & PF_W)
|
| + prot |= PROT_WRITE;
|
| + if (phdr->p_flags & PF_X)
|
| + prot |= PROT_EXEC;
|
| + return prot;
|
| +}
|
| +
|
| +static uintptr_t round_up(uintptr_t value, uintptr_t size) {
|
| + return (value + size - 1) & -size;
|
| +}
|
| +
|
| +static uintptr_t round_down(uintptr_t value, uintptr_t size) {
|
| + return value & -size;
|
| +}
|
| +
|
| +/*
|
| + * Handle the "bss" portion of a segment, where the memory size
|
| + * exceeds the file size and we zero-fill the difference. For any
|
| + * whole pages in this region, we over-map anonymous pages. For the
|
| + * sub-page remainder, we zero-fill bytes directly.
|
| */
|
| +static void handle_bss(unsigned int segnum, const ElfW(Phdr) *ph,
|
| + ElfW(Addr) load_bias, size_t pagesize) {
|
| + if (ph->p_memsz > ph->p_filesz) {
|
| + ElfW(Addr) file_end = ph->p_vaddr + load_bias + ph->p_filesz;
|
| + ElfW(Addr) file_page_end = round_up(file_end, pagesize);
|
| + ElfW(Addr) page_end = round_up(ph->p_vaddr + load_bias +
|
| + ph->p_memsz, pagesize);
|
| + if (page_end > file_page_end)
|
| + my_mmap("bss segment", segnum,
|
| + file_page_end, page_end - file_page_end,
|
| + prot_from_phdr(ph), MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
|
| + if (file_page_end > file_end && (ph->p_flags & PF_W))
|
| + my_bzero((void *) file_end, file_page_end - file_end);
|
| + }
|
| +}
|
| +
|
| +/*
|
| + * This is the main loading code. It's called with the address of the
|
| + * auxiliary vector on the stack, which we need to examine and modify.
|
| + * It returns the dynamic linker's runtime entry point address, where
|
| + * we should jump to. This is called by the machine-dependent _start
|
| + * code (below). On return, it restores the original stack pointer
|
| + * and jumps to this entry point.
|
| + */
|
| +ElfW(Addr) do_load(ElfW(auxv_t) *auxv) {
|
| + /*
|
| + * Record the auxv entries that are specific to the file loaded.
|
| + * The incoming entries point to our own static executable.
|
| + */
|
| + ElfW(auxv_t) *av_entry = NULL;
|
| + ElfW(auxv_t) *av_phdr = NULL;
|
| + ElfW(auxv_t) *av_phnum = NULL;
|
| + size_t pagesize = 0;
|
| +
|
| + ElfW(auxv_t) *av;
|
| + for (av = auxv;
|
| + av_entry == NULL || av_phdr == NULL || av_phnum == NULL || pagesize == 0;
|
| + ++av) {
|
| + switch (av->a_type) {
|
| + case AT_NULL:
|
| + fail("Failed to find AT_ENTRY, AT_PHDR, AT_PHNUM, or AT_PAGESZ!",
|
| + NULL, 0, NULL, 0);
|
| + /*NOTREACHED*/
|
| + break;
|
| + case AT_ENTRY:
|
| + av_entry = av;
|
| + break;
|
| + case AT_PAGESZ:
|
| + pagesize = av->a_un.a_val;
|
| + break;
|
| + case AT_PHDR:
|
| + av_phdr = av;
|
| + break;
|
| + case AT_PHNUM:
|
| + av_phnum = av;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + int fd = my_open(DYNAMIC_LINKER, O_RDONLY);
|
| +
|
| + ElfW(Ehdr) ehdr;
|
| + my_pread("Failed to read ELF header from dynamic linker! ",
|
| + fd, &ehdr, sizeof(ehdr), 0);
|
| +
|
| + 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_version != EV_CURRENT ||
|
| + ehdr.e_ehsize != sizeof(ehdr) ||
|
| + ehdr.e_phentsize != sizeof(ElfW(Phdr)))
|
| + fail("Dynamic linker has no valid ELF header!", NULL, 0, NULL, 0);
|
| +
|
| + switch (ehdr.e_machine) {
|
| +#if defined(__i386__)
|
| + case EM_386:
|
| +#elif defined(__x86_64__)
|
| + case EM_X86_64:
|
| +#elif defined(__arm__)
|
| + case EM_ARM:
|
| +#else
|
| +# error "Don't know the e_machine value for this architecture!"
|
| +#endif
|
| + break;
|
| + default:
|
| + fail("Dynamic linker has wrong architecture! ",
|
| + "e_machine", ehdr.e_machine, NULL, 0);
|
| + break;
|
| + }
|
| +
|
| + ElfW(Phdr) phdr[MAX_PHNUM];
|
| + if (ehdr.e_phnum > sizeof(phdr) / sizeof(phdr[0]) || ehdr.e_phnum < 1)
|
| + fail("Dynamic linker has unreasonable ",
|
| + "e_phnum", ehdr.e_phnum, NULL, 0);
|
|
|
| -#include <stdlib.h>
|
| + if (ehdr.e_type != ET_DYN)
|
| + fail("Dynamic linker not ET_DYN! ",
|
| + "e_type", ehdr.e_type, NULL, 0);
|
|
|
| -/* reserve 1GB of space */
|
| -#define ONEGIG (1 << 30)
|
| -char nacl_reserved_space[ONEGIG];
|
| + my_pread("Failed to read program headers from dynamic linker! ",
|
| + fd, phdr, sizeof(phdr[0]) * ehdr.e_phnum, ehdr.e_phoff);
|
|
|
| -void nacl_helper_init(int argc, char *argv[],
|
| - const char *nacl_reserved_space);
|
| + size_t i = 0;
|
| + while (i < ehdr.e_phnum && phdr[i].p_type != PT_LOAD)
|
| + ++i;
|
| + if (i == ehdr.e_phnum)
|
| + fail("Dynamic linker has no PT_LOAD header!",
|
| + NULL, 0, NULL, 0);
|
|
|
| -int main(int argc, char *argv[]) {
|
| - nacl_helper_init(argc, argv, nacl_reserved_space);
|
| - abort();
|
| - return 0; // convince the tools I'm sane.
|
| + /*
|
| + * 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.
|
| + */
|
| + const ElfW(Phdr) *first_load = &phdr[i];
|
| + const ElfW(Phdr) *last_load = &phdr[ehdr.e_phnum - 1];
|
| + while (last_load > first_load && last_load->p_type != PT_LOAD)
|
| + --last_load;
|
| +
|
| + size_t span = last_load->p_vaddr + last_load->p_memsz - first_load->p_vaddr;
|
| +
|
| + /*
|
| + * Map the first segment and reserve the space used for the rest and
|
| + * for holes between segments.
|
| + */
|
| + const uintptr_t mapping = my_mmap("segment", first_load - phdr,
|
| + round_down(first_load->p_vaddr, pagesize),
|
| + span, prot_from_phdr(first_load),
|
| + MAP_PRIVATE, fd,
|
| + round_down(first_load->p_offset, pagesize));
|
| +
|
| + const ElfW(Addr) load_bias = mapping - round_down(first_load->p_vaddr,
|
| + pagesize);
|
| +
|
| + if (first_load->p_offset > ehdr.e_phoff ||
|
| + first_load->p_filesz < ehdr.e_phoff + (ehdr.e_phnum * sizeof(ElfW(Phdr))))
|
| + fail("First load segment of dynamic linker does not contain phdrs!",
|
| + NULL, 0, NULL, 0);
|
| +
|
| + /* Point the auxv elements at the dynamic linker's phdrs and entry. */
|
| + av_phdr->a_un.a_val = (ehdr.e_phoff - first_load->p_offset +
|
| + first_load->p_vaddr + load_bias);
|
| + av_phnum->a_un.a_val = ehdr.e_phnum;
|
| + av_entry->a_un.a_val = ehdr.e_entry + load_bias;
|
| +
|
| + handle_bss(first_load - phdr, first_load, load_bias, pagesize);
|
| +
|
| + ElfW(Addr) last_end = first_load->p_vaddr + load_bias + first_load->p_memsz;
|
| +
|
| + /*
|
| + * Map the remaining segments, and protect any holes between them.
|
| + */
|
| + const ElfW(Phdr) *ph;
|
| + for (ph = first_load + 1; ph <= last_load; ++ph) {
|
| + if (ph->p_type == PT_LOAD) {
|
| + ElfW(Addr) last_page_end = round_up(last_end, pagesize);
|
| +
|
| + last_end = ph->p_vaddr + load_bias + ph->p_memsz;
|
| + ElfW(Addr) start = round_down(ph->p_vaddr + load_bias, pagesize);
|
| + ElfW(Addr) end = round_up(last_end, pagesize);
|
| +
|
| + if (start > last_page_end)
|
| + my_mprotect(ph - phdr, last_page_end, start - last_page_end, PROT_NONE);
|
| +
|
| + my_mmap("segment", ph - phdr,
|
| + start, end - start,
|
| + prot_from_phdr(ph), MAP_PRIVATE | MAP_FIXED, fd,
|
| + round_down(ph->p_offset, pagesize));
|
| +
|
| + handle_bss(ph - phdr, ph, load_bias, pagesize);
|
| + }
|
| + }
|
| +
|
| + sys_close(fd);
|
| +
|
| + return ehdr.e_entry + load_bias;
|
| }
|
| +
|
| +/*
|
| + * We have to define the actual entry point code (_start) in assembly
|
| + * for each machine. The kernel startup protocol is not compatible
|
| + * with the normal C function calling convention. Here, we calculate
|
| + * the address of the auxiliary vector on the stack; call do_load
|
| + * (above) using the normal C convention as per the ABI; restore the
|
| + * original starting stack; and finally, jump to the dynamic linker's
|
| + * entry point address.
|
| + */
|
| +#if defined(__i386__)
|
| +asm(".globl _start\n"
|
| + ".type _start,@function\n"
|
| + "_start:\n"
|
| + "xorl %ebp, %ebp\n"
|
| + "movl %esp, %ebx\n" /* Save starting SP in %ebx. */
|
| + "andl $-16, %esp\n" /* Align the stack as per ABI. */
|
| + "movl (%ebx), %eax\n" /* argc */
|
| + "leal 8(%ebx,%eax,4), %ecx\n" /* envp */
|
| + /* Find the envp element that is NULL, and auxv is past there. */
|
| + "0: addl $4, %ecx\n"
|
| + "cmpl $0, -4(%ecx)\n"
|
| + "jne 0b\n"
|
| + "pushl %ecx\n" /* Argument: auxv. */
|
| + "call do_load\n"
|
| + "movl %ebx, %esp\n" /* Restore the saved SP. */
|
| + "jmp *%eax\n" /* Jump to the entry point. */
|
| + );
|
| +#elif defined(__x86_64__)
|
| +asm(".globl _start\n"
|
| + ".type _start,@function\n"
|
| + "_start:\n"
|
| + "xorq %rbp, %rbp\n"
|
| + "movq %rsp, %rbx\n" /* Save starting SP in %rbx. */
|
| + "andq $-16, %rsp\n" /* Align the stack as per ABI. */
|
| + "movq (%rbx), %rax\n" /* argc */
|
| + "leaq 16(%rbx,%rax,8), %rdi\n" /* envp */
|
| + /* Find the envp element that is NULL, and auxv is past there. */
|
| + "0: addq $8, %rdi\n"
|
| + "cmpq $0, -8(%rdi)\n"
|
| + "jne 0b\n"
|
| + "call do_load\n" /* Argument already in %rdi: auxv */
|
| + "movq %rbx, %rsp\n" /* Restore the saved SP. */
|
| + "jmp *%rax\n" /* Jump to the entry point. */
|
| + );
|
| +#elif defined(__arm__)
|
| +asm(".globl _start\n"
|
| + ".type _start,#function\n"
|
| + "_start:\n"
|
| +#if defined(__thumb2__)
|
| + ".thumb\n"
|
| + ".syntax unified\n"
|
| +#endif
|
| + "mov fp, #0\n"
|
| + "mov lr, #0\n"
|
| + "mov r4, sp\n" /* Save starting SP in r4. */
|
| + "ldr r1, [r4]\n" /* argc */
|
| + "add r1, r1, #2\n"
|
| + "add r0, r4, r1, asl #2\n" /* envp */
|
| + /* Find the envp element that is NULL, and auxv is past there. */
|
| + "0: ldr r1, [r0], #4\n"
|
| + "cmp r1, #0\n"
|
| + "bne 0b\n"
|
| + "bl do_load\n"
|
| + "mov sp, r4\n" /* Restore the saved SP. */
|
| + "blx r0\n" /* Jump to the entry point. */
|
| + );
|
| +#else
|
| +# error "Need stack-preserving _start code for this architecture!"
|
| +#endif
|
|
|