/* Copyright (c) 2008, Sandia National Laboratories */ #include #include /** * Verifies that an ELF header is sane. * Returns 0 if header is sane and random non-zero values if header is insane. */ int elf_check_hdr(const struct elfhdr *hdr) { if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) != 0) { print(TYPE_ERR "bad e_ident %#x\n", *((unsigned int *)hdr->e_ident)); return -1; } if (hdr->e_ident[EI_CLASS] != ELF_CLASS) { print(TYPE_ERR "bad e_ident[EI_CLASS] %#x\n", (unsigned int)hdr->e_ident[EI_CLASS]); return -1; } if (hdr->e_ident[EI_DATA] != ELF_DATA) { print(TYPE_ERR "bad e_ident[EI_DATA] %#x\n", (unsigned int)hdr->e_ident[EI_DATA]); return -1; } if (hdr->e_ident[EI_VERSION] != EV_CURRENT) { print(TYPE_ERR "bad e_ident[EI_VERSION] %#x\n", (unsigned int)hdr->e_ident[EI_VERSION]); return -1; } if (hdr->e_ident[EI_OSABI] != ELF_OSABI) { print(TYPE_ERR "bad e_dent[EI_OSABI] %#x\n", (unsigned int)hdr->e_ident[EI_OSABI]); return -1; } if (hdr->e_type != ET_EXEC) { print(TYPE_ERR "bad e_type %#x\n", (unsigned int)hdr->e_type); return -1; } if (hdr->e_machine != ELF_ARCH) { print(TYPE_ERR "bad e_machine %#x\n", (unsigned int)hdr->e_machine); return -1; } if (hdr->e_version != EV_CURRENT) { print(TYPE_ERR "bad e_version %#x\n", (unsigned int)hdr->e_version); return -1; } if (hdr->e_flags != 0) { print(TYPE_ERR "bad e_flags %#x\n", (unsigned int)hdr->e_flags); return -1; } return 0; } /** * Prints the contents of an ELF file header to the console. */ void elf_print_elfhdr(const struct elfhdr *hdr) { print(TYPE_NORM "ELF File Header:\n"); print(TYPE_NORM " type %0#10x\n", (unsigned int) hdr->e_type ); print(TYPE_NORM " machine %0#10x\n", (unsigned int) hdr->e_machine ); print(TYPE_NORM " version %0#10x\n", (unsigned int) hdr->e_version ); print(TYPE_NORM " entry %0#18lx\n", (unsigned long) hdr->e_entry ); print(TYPE_NORM " phoff %0#18lx\n", (unsigned long) hdr->e_phoff ); print(TYPE_NORM " shoff %0#18lx\n", (unsigned long) hdr->e_shoff ); print(TYPE_NORM " flags %0#10x\n", (unsigned int) hdr->e_flags ); print(TYPE_NORM " ehsize %0#10x\n", (unsigned int) hdr->e_ehsize ); print(TYPE_NORM " phentsize %0#10x\n", (unsigned int) hdr->e_phentsize ); print(TYPE_NORM " phnum %0#10x\n", (unsigned int) hdr->e_phnum ); print(TYPE_NORM " shentsize %0#10x\n", (unsigned int) hdr->e_shentsize ); print(TYPE_NORM " shnum %0#10x\n", (unsigned int) hdr->e_shnum ); print(TYPE_NORM " shstrndx %0#10x\n", (unsigned int) hdr->e_shstrndx ); } /** * Prints the contents of an ELF program header to the console. */ void elf_print_phdr(const struct elf_phdr *hdr) { char *name; switch (hdr->p_type) { case PT_NULL: name = "NULL"; break; case PT_LOAD: name = "LOAD"; break; case PT_DYNAMIC: name = "DYNAMIC"; break; case PT_INTERP: name = "INTERP"; break; case PT_NOTE: name = "NOTE"; break; case PT_SHLIB: name = "SHLIB"; break; case PT_PHDR: name = "PHDR"; break; case PT_LOPROC: name = "LOPROC"; break; case PT_HIPROC: name = "HIPROC"; break; default: name = "UNDEFINED TYPE"; } print(TYPE_NORM "ELF Program Segment Header:\n"); print(TYPE_NORM " type %s\n", name); print(TYPE_NORM " flags %0#10x\n", (unsigned int) hdr->p_flags ); print(TYPE_NORM " offset %0#18lx\n", (unsigned long) hdr->p_offset ); print(TYPE_NORM " vaddr %0#18lx\n", (unsigned long) hdr->p_vaddr ); print(TYPE_NORM " paddr %0#18lx\n", (unsigned long) hdr->p_paddr ); print(TYPE_NORM " filesz %0#18lx\n", (unsigned long) hdr->p_filesz ); print(TYPE_NORM " memsz %0#18lx\n", (unsigned long) hdr->p_memsz ); print(TYPE_NORM " align %0#18lx\n", (unsigned long) hdr->p_align ); } /** * Converts ELF flags to the corresponding kernel memory subsystem flags. */ vmflags_t elf_pflags_to_vmflags(unsigned int elf_pflags) { vmflags_t vmflags = VM_USER; if ( elf_pflags & PF_R ) vmflags |= VM_READ; if ( elf_pflags & PF_W ) vmflags |= VM_WRITE; if ( elf_pflags & PF_X ) vmflags |= VM_EXEC; return vmflags; } /** * Determines an ELF executable's entry point... where to start executing. * Note: The address returned is in the context of the executing ELF * image, not an address within the passed in elf_image. */ vaddr_t elf_entry_point(const void *elf_image) { const struct elfhdr *ehdr = elf_image; return ehdr->e_entry; } /** * Determines the address of an ELF executable's program header table. * Note: The address returned is in the context of the executing ELF * image, not an address within the passed in elf_image. */ vaddr_t elf_phdr_table_addr(const void *elf_image) { const struct elfhdr *ehdr = elf_image; struct elf_phdr *phdr_array, *phdr; unsigned int i; phdr_array = (struct elf_phdr *)(elf_image + ehdr->e_phoff); for (i = 0; i < ehdr->e_phnum; i++) { phdr = &phdr_array[i]; if (phdr->p_type == PT_LOAD) return phdr->p_vaddr - phdr->p_offset + ehdr->e_phoff; } return 0; } /** * Returns the number of entries in an ELF executable's program header table. */ unsigned int elf_num_phdrs(const void *elf_image) { const struct elfhdr *ehdr = elf_image; return ehdr->e_phnum; } /** * Determines where the UNIX heap should start for a given ELF executable. * Note: The address returned is in the context of the executing ELF * image, not an address relative to the passed in elf_image. */ vaddr_t elf_heap_start(const void *elf_image) { const struct elfhdr *ehdr; const struct elf_phdr *phdr_array; const struct elf_phdr *phdr; vaddr_t end, heap_start=0; size_t i; /* Locate the program header array (in this context) */ ehdr = elf_image; phdr_array = (struct elf_phdr *)(elf_image + ehdr->e_phoff); for (i = 0; i < ehdr->e_phnum; i++) { phdr = &phdr_array[i]; if (phdr->p_type != PT_LOAD) continue; /* Calculate the end of the LOAD segment in memory */ end = phdr->p_vaddr + phdr->p_memsz; if (end > heap_start) heap_start = end; } return heap_start; } /** * Given an argument string like "arg1=foo arg2=bar", parses it into * an argv[] or envp[] style array of string pointers. Useful for * constructing the argv and envp arguments to elf_init_stack(). */ int elf_init_str_array( size_t size, char * ptrs[], char * str ) { size_t pos = 0; char *tmp; while (strlen(str)) { /* move past white space */ while (*str && isspace(*str)) ++str; tmp = str; /* find the end of the string */ while (*str && !isspace(*str)) ++str; *str++ = 0; if (strlen(tmp)) { if (pos == size - 1) return -1; ptrs[pos++] = tmp; } } ptrs[pos] = ""; return 0; } /** * Writes an auxiliary info table entry. */ static void write_aux( struct aux_ent * table, int index, unsigned long id, unsigned long val ) { table[index].id = id; table[index].val = val; } /** * Determines the value of the current stack pointer in the target * address space. The sp argument is the stack pointer in this context * (i.e., the context this code is executing in), stack_mapping is the * address of the stack in this context, stack_start is the address * of the stack in the target address space, and extent is the size * of the stack. */ static vaddr_t sp_in_aspace(void *sp, void *stack_mapping, vaddr_t stack_start, size_t extent) { vaddr_t stack_end = (vaddr_t)stack_mapping + extent; vaddr_t stack_vend = stack_start + extent; size_t stack_offset = stack_end - (vaddr_t)sp; return stack_vend - stack_offset; } /** * Sets up the initial stack for a new task. This includes storing the * argv[] argument array, envp[] environment array, and auxiliary info table * to the top of the user stack in the format that the C library expects them. * Eventually the arguments get passed to the application's * main(argc, argv, envp) function. * * If successful, the initial stack pointer value that should be used when * starting the new task is returned in >stack_ptr. * * This function sets up the initial stack as follows (stack grows down): * * Environment Strings * Argument Strings * Platform String * Auxiliary Info Table * envp[] * argv[] * argc * * Arguments: * [IN] elf_image The ELF executable, needed to setup aux info. * [IN] stack_mapping Where the stack is mapped in this context. * [IN] stack_start Where the stack is located in the target aspace. * [IN] stack_extent Size of the stack. * [IN] argv[] Array of pointers to argument strings. * [IN] envp[] Array of pointers to environment strings. * [IN] uid User ID of the task * [IN] gid Group ID of the task * [IN] hwcap Hardware capability bitfield * (used for AT_HWCAP entry in aux info table) * [OUT] stack_ptr The initial stack pointer value for the new task. * (note this is an address in the target aspace) * * Returns: * Success: 0 * Failure: Error Code, stack may have been partially initialized */ int elf_init_stack( void * elf_image, void * stack_mapping, vaddr_t stack_start, size_t stack_extent, char * argv[], char * envp[], uid_t uid, gid_t gid, uint32_t hwcap, vaddr_t * stack_ptr ) { size_t i, len; uintptr_t sp; const char *platform_str = ELF_PLATFORM; struct aux_ent auxv[AT_ENTRIES]; size_t argc=0, envc=0, auxc=0; size_t arg_len=0, env_len=0, auxv_len=0; size_t platform_str_len=0; char *strings_sp; char *platform_str_sp; unsigned long *auxv_sp; unsigned long *envp_sp; unsigned long *argv_sp; unsigned long *argc_sp; /* Count # of arguments and their total string length */ while ((len = strlen(argv[argc])) != 0) { arg_len += (len + 1); ++argc; } /* Count # of environment variables and their total string length */ while ((len = strlen(envp[envc])) != 0) { env_len += (len + 1); ++envc; } /* Calculate length of the arch's platform string, if there is one */ if (platform_str) platform_str_len = strlen(platform_str) + 1; /* Make room on stack for arg, env, and platform strings */ sp = (uintptr_t)((vaddr_t)stack_mapping + stack_extent); sp -= (arg_len + env_len + platform_str_len); strings_sp = (void *) sp; /* Build the auxilliary information table */ write_aux(auxv, auxc++, AT_HWCAP, hwcap); write_aux(auxv, auxc++, AT_PAGESZ, ELF_EXEC_PAGESIZE); write_aux(auxv, auxc++, AT_CLKTCK, 1000000l); write_aux(auxv, auxc++, AT_PHDR, elf_phdr_table_addr(elf_image)); write_aux(auxv, auxc++, AT_PHENT, sizeof(struct elf_phdr)); write_aux(auxv, auxc++, AT_PHNUM, elf_num_phdrs(elf_image)); write_aux(auxv, auxc++, AT_BASE, 0); write_aux(auxv, auxc++, AT_FLAGS, 0); write_aux(auxv, auxc++, AT_ENTRY, elf_entry_point(elf_image)); write_aux(auxv, auxc++, AT_UID, uid); write_aux(auxv, auxc++, AT_EUID, uid); write_aux(auxv, auxc++, AT_GID, gid); write_aux(auxv, auxc++, AT_EGID, gid); write_aux(auxv, auxc++, AT_SECURE, 0); if (platform_str) { platform_str_sp = strings_sp; write_aux( auxv, auxc++, AT_PLATFORM, sp_in_aspace(platform_str_sp, stack_mapping, stack_start, stack_extent) ); } write_aux(auxv, auxc++, AT_NULL, 0); /* Make room on stack for aux info table */ auxv_len = auxc * sizeof(struct aux_ent); sp -= auxv_len; /* Make room on stack for argc, argv[], envp[] */ sp -= ((1 + (argc + 1) + (envc + 1)) * sizeof(unsigned long)); /* Align stack to 16-byte boundary */ sp = round_down(sp, 16); /* Calculate stack address to store argc, argv[], envp[], and auxv[] */ argc_sp = (unsigned long *) sp; argv_sp = argc_sp + 1; envp_sp = argv_sp + argc + 1; auxv_sp = envp_sp + envc + 1; /* Store arch's platform string, if there is one */ if (platform_str) { memcpy(strings_sp, platform_str, platform_str_len); strings_sp += platform_str_len; } /* Store the auxiliary information array */ memcpy(auxv_sp, auxv, auxv_len); /* Store argv[] */ for (i = 0; i < argc; i++) { len = strlen(argv[i]) + 1; memcpy(strings_sp, argv[i], len); argv_sp[i] = sp_in_aspace(strings_sp, stack_mapping, stack_start, stack_extent), strings_sp += len; } argv_sp[i] = 0; /* NULL terminate argv[] */ /* Store envp[] */ for (i = 0; i < envc; i++) { len = strlen(envp[i]) + 1; memcpy(strings_sp, envp[i], len); envp_sp[i] = sp_in_aspace(strings_sp, stack_mapping, stack_start, stack_extent), strings_sp += len; } envp_sp[i] = 0; /* NULL terminate argv[] */ /* Store argc */ *argc_sp = argc; if (stack_ptr) { *stack_ptr = sp_in_aspace((void *)sp, stack_mapping, stack_start, stack_extent); } return 0; } /** * A "default" alloc_pmem() function for use with elf_load_executable(). * A user may wish to define a custom replacement alloc_pmem() function * to, for example, keep track of the physical memory that is allocated. */ paddr_t elf_dflt_alloc_pmem(size_t size, size_t alignment, uintptr_t arg) { struct pmem_region result; if (pmem_alloc_umem(size, alignment, &result)) return 0; /* Mark the memory as being used by the init task */ result.type = PMEM_TYPE_INIT_TASK; BUG_ON(pmem_update(&result)); return result.start; } static int load_writable_segment( void * elf_image, struct elf_phdr * phdr, id_t aspace_id, vaddr_t start, size_t extent, vmpagesize_t pagesz, uintptr_t alloc_pmem_arg, paddr_t (*alloc_pmem)(size_t size, size_t alignment, uintptr_t arg) ) { int status; paddr_t pmem; vaddr_t local_start; vaddr_t src, dst; id_t my_aspace_id; /* Figure out my address space ID */ if ((status = aspace_get_myid(&my_aspace_id))) return status; /* Allocate physical memory for the segment */ if (!(pmem = alloc_pmem(extent, pagesz, alloc_pmem_arg))) return -ENOMEM; /* Map the segment into the target address space */ status = aspace_map_region( aspace_id, start, extent, elf_pflags_to_vmflags(phdr->p_flags), pagesz, "ELF", pmem ); if (status) return status; /* Map the segment into this address space */ status = aspace_map_region_anywhere( my_aspace_id, &local_start, extent, (VM_USER|VM_READ|VM_WRITE), pagesz, "temporary", pmem ); if (status) return status; /* Copy segment data from ELF image into the target address space * (via its temporary mapping in our address space) */ dst = local_start + (phdr->p_vaddr - start); src = (vaddr_t)elf_image + phdr->p_offset; memcpy((void *)dst, (void *)src, phdr->p_filesz); /* Unmap the segment from this address space */ status = aspace_del_region(my_aspace_id, local_start, extent); if (status) return status; return 0; } static int load_readonly_segment( paddr_t elf_image_paddr, struct elf_phdr * phdr, id_t aspace_id, vaddr_t start, size_t extent, vmpagesize_t pagesz ) { return aspace_map_region( aspace_id, start, extent, elf_pflags_to_vmflags(phdr->p_flags), pagesz, "ELF (mapped)", elf_image_paddr + round_down(phdr->p_offset, pagesz) ); } /** * Loads an ELF executable image into the specified address space. * * Arguments: * [IN] elf_image: Location of ELF image in this address space. * [IN] elf_image_paddr: Location of ELF image in physical memory. * [IN] aspace_id: Address space to load ELF image into. * [IN] pagesz: Page size to use when mapping ELF image. * [IN] alloc_pmem_arg: Argument to pass to alloc_pmem(). * [IN] alloc_pmem: Function pointer to use to allocate physical * memory for the region. alloc_mem() returns * the physical address of the memory allocated. * * Returns: * Success: 0 * Failure: Error Code, the target address space is left in an * undefined state and should be destroyed. */ int elf_load_executable( void * elf_image, paddr_t elf_image_paddr, id_t aspace_id, vmpagesize_t pagesz, uintptr_t alloc_pmem_arg, paddr_t (*alloc_pmem)(size_t size, size_t alignment, uintptr_t arg) ) { struct elfhdr * ehdr; struct elf_phdr * phdr_array; struct elf_phdr * phdr; size_t i; vaddr_t start, end; size_t extent; size_t num_load_segments=0; int status; /* Locate the program header array (in this context) */ ehdr = elf_image; phdr_array = (struct elf_phdr *)(elf_image + ehdr->e_phoff); /* Set up a region for each program segment */ for (i = 0; i < ehdr->e_phnum; i++) { phdr = &phdr_array[i]; if (phdr->p_type != PT_LOAD) continue; /* Calculate the segment's bounds */ start = round_down(phdr->p_vaddr, pagesz); end = round_up(phdr->p_vaddr + phdr->p_memsz, pagesz); extent = end - start; if (phdr->p_flags & PF_W) { /* Writable segments must be copied into the * target address space */ status = load_writable_segment( elf_image, phdr, aspace_id, start, extent, pagesz, alloc_pmem_arg, alloc_pmem ); if (status) return status; } else { /* Read-only segments are mapped directly * from the ELF image */ status = load_readonly_segment( elf_image_paddr, phdr, aspace_id, start, extent, pagesz ); if (status) return status; } ++num_load_segments; } return (num_load_segments) ? 0 : -ENOENT; } static int make_region( id_t aspace_id, vaddr_t start, size_t extent, vmflags_t flags, vmpagesize_t pagesz, const char * name, uintptr_t alloc_pmem_arg, paddr_t (*alloc_pmem)(size_t size, size_t alignment, uintptr_t arg), paddr_t * pmem ) { int status; *pmem = alloc_pmem(extent, pagesz, alloc_pmem_arg); if (*pmem == 0) { print("Failed to allocate physical memory for %s.", name); return -ENOMEM; } status = aspace_map_region(aspace_id, start, extent, flags, pagesz, name, *pmem); if (status) { print("Failed to map physical memory for %s (status=%d).", name, status); return status; } return 0; } /** * Maximum number of arguments and environment variables that may * be passed to the new task created by elf_load(). */ #define MAX_ARGC 32 #define MAX_ENVC 32 /** * Kitchen-sink ELF image load function. * If something more custom is desired, this function can be used as a guide * for what needs to be done to load an ELF executable and setup the * accompanying address space. */ int elf_load( void * elf_image, paddr_t elf_image_paddr, const char * name, id_t desired_aspace_id, vmpagesize_t pagesz, size_t heap_size, size_t stack_size, char * argv_str, char * envp_str, start_state_t * start_state, uintptr_t alloc_pmem_arg, paddr_t (*alloc_pmem)(size_t size, size_t alignment, uintptr_t arg) ) { int status; char *argv[MAX_ARGC] = { (char *)name }; char *envp[MAX_ENVC]; id_t my_aspace_id, aspace_id; vaddr_t heap_start, stack_start, stack_end, stack_ptr; vaddr_t local_stack_start; size_t heap_extent, stack_extent; paddr_t heap_pmem, stack_pmem; uint32_t hwcap; if (!elf_image || !start_state || !alloc_pmem) return -EINVAL; if (elf_init_str_array(MAX_ARGC-1, argv+1, argv_str)) { print("Too many ARGV strings."); return -EINVAL; } if (elf_init_str_array(MAX_ENVC, envp, envp_str)) { print("Too many ENVP strings."); return -EINVAL; } if ((status = aspace_create(desired_aspace_id, "init_task", &aspace_id))) { print("Failed to create aspace (status=%d).", status); return status; } /* Load the ELF executable's LOAD segments */ status = elf_load_executable( elf_image, /* where I can access the ELF image */ elf_image_paddr, /* where it is in physical memory */ aspace_id, /* the address space to map it into */ pagesz, /* page size to map it with */ 0, /* arg to pass to alloc_pmem */ alloc_pmem /* func to use to allocate phys mem */ ); if (status) { print("Failed to load ELF image (status=%d).", status); return status; } /* Create the UNIX heap */ heap_start = round_up(elf_heap_start(elf_image), pagesz); heap_extent = round_up(heap_size, pagesz); status = make_region( aspace_id, heap_start, heap_extent, (VM_USER|VM_READ|VM_WRITE|VM_EXEC|VM_HEAP), pagesz, "heap", alloc_pmem_arg, alloc_pmem, &heap_pmem ); if (status) { print("Failed to create heap (status=%d).", status); return status; } /* Create the stack region */ stack_end = SMARTMAP_ALIGN; stack_start = round_down(stack_end - stack_size, pagesz); stack_extent = stack_end - stack_start; status = make_region( aspace_id, stack_start, stack_extent, (VM_USER|VM_READ|VM_WRITE|VM_EXEC), pagesz, "stack", alloc_pmem_arg, alloc_pmem, &stack_pmem ); if (status) { print("Failed to create stack (status=%d).", status); return status; } /* Map the stack region into this address space */ if ((status = aspace_get_myid(&my_aspace_id))) return status; status = aspace_map_region_anywhere( my_aspace_id, &local_stack_start, stack_extent, (VM_USER|VM_READ|VM_WRITE), pagesz, "temporary", stack_pmem ); if (status) { print("Failed to map stack locally (status=%d).", status); return status; } /* Initialize the stack */ status = elf_hwcap(start_state->cpu_id, &hwcap); if (status) { print("Failed to get hw capabilities (status=%d).", status); return status; } status = elf_init_stack( elf_image, (void *)local_stack_start, /* Where I can access it */ stack_start, /* Where it is in target aspace */ stack_extent, argv, envp, start_state->uid, start_state->gid, hwcap, &stack_ptr ); if (status) { print("Failed to initialize stack (status=%d).", status); return status; } start_state->aspace_id = aspace_id; start_state->entry_point = elf_entry_point(elf_image); start_state->stack_ptr = stack_ptr; return 0; }