diff options
Diffstat (limited to 'sbin/modload/elf.c')
-rw-r--r-- | sbin/modload/elf.c | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/sbin/modload/elf.c b/sbin/modload/elf.c new file mode 100644 index 00000000000..4ff43facc46 --- /dev/null +++ b/sbin/modload/elf.c @@ -0,0 +1,494 @@ +/* $OpenBSD: elf.c,v 1.1 2002/01/08 21:28:38 ericj Exp $ */ +/* $NetBSD: elf.c,v 1.8 2002/01/03 21:45:58 jdolecek Exp $ */ + +/* + * Copyright (c) 1998 Johan Danielsson <joda@pdc.kth.se> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/param.h> + +#if defined(__alpha__) || defined(__arch64__) || defined(__x86_64__) +#define ELFSIZE 64 +#else +#define ELFSIZE 32 +#endif +#include <sys/exec_elf.h> +#ifndef ELF_HDR_SIZE +#define ELF_HDR_SIZE sizeof(Elf_Ehdr) +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/lkm.h> + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include "modload.h" + +char *strtab; + +static void +read_section_header(int fd, Elf_Ehdr *ehdr, int num, Elf_Shdr *shdr) +{ + + if (lseek(fd, ehdr->e_shoff + num * ehdr->e_shentsize, SEEK_SET) < 0) + err(1, "lseek"); + if (read(fd, shdr, sizeof(*shdr)) != sizeof(*shdr)) + err(1, "read"); +} + +struct elf_section { + char *name; /* name of section; points into string table */ + unsigned long type; /* type of section */ + void *addr; /* load address of section */ + off_t offset; /* offset in file */ + size_t size; /* size of section */ + size_t align; + struct elf_section *next; +}; + +/* adds the section `s' at the correct (sorted by address) place in + the list ponted to by head; *head may be NULL */ +static void +add_section(struct elf_section **head, struct elf_section *s) +{ + struct elf_section *p, **q; + q = head; + p = *head; + + while (1) { + if (p == NULL || p->addr > s->addr) { + s->next = p; + *q = s; + return; + } + q = &p->next; + p = p->next; + } +} + +/* make a linked list of all sections containing ALLOCatable data */ +static void +read_sections(int fd, Elf_Ehdr *ehdr, char *shstrtab, struct elf_section **head) +{ + int i; + Elf_Shdr shdr; + + *head = NULL; + /* scan through section headers */ + for (i = 0; i < ehdr->e_shnum; i++) { + struct elf_section *s; + read_section_header(fd, ehdr, i, &shdr); + if (((shdr.sh_flags & SHF_ALLOC) == 0) + && (shdr.sh_type != SHT_STRTAB) + && (shdr.sh_type != SHT_SYMTAB) + && (shdr.sh_type != SHT_DYNSYM)) { + /* skip non-ALLOC sections */ + continue; + } + s = malloc(sizeof(*s)); + if (s == NULL) + errx(1, "failed to allocate %lu bytes", + (u_long)sizeof(*s)); + s->name = shstrtab + shdr.sh_name; + s->type = shdr.sh_type; + s->addr = (void*)shdr.sh_addr; + s->offset = shdr.sh_offset; + s->size = shdr.sh_size; + s->align = shdr.sh_addralign; + add_section(head, s); + } +} + +/* get the symbol table sections and free the rest of them */ +static void +get_symtab(struct elf_section **symtab) +{ + struct elf_section *head, *cur, *prev; + + head = NULL; + prev = NULL; + cur = *symtab; + while (cur) { + if ((cur->type == SHT_SYMTAB) || (cur->type == SHT_DYNSYM)) { + if (head == NULL) { + head = cur; + } + if (prev != NULL) { + prev->next = cur; + } + prev = cur; + cur = cur->next; + } else { + struct elf_section *p = cur; + cur = cur->next; + p->next = NULL; + free(p); + } + } + + if (prev) { + prev->next = NULL; + } + *symtab = head; +} + +/* free a list of section headers */ +static void +free_sections(struct elf_section *head) +{ + + while (head) { + struct elf_section *p = head; + head = head->next; + free(p); + } +} + +/* read section header's string table */ +static char * +read_shstring_table(int fd, Elf_Ehdr *ehdr) +{ + Elf_Shdr shdr; + char *shstrtab; + + read_section_header(fd, ehdr, ehdr->e_shstrndx, &shdr); + + shstrtab = malloc(shdr.sh_size); + if (shstrtab == NULL) + errx(1, "failed to allocate %lu bytes", (u_long)shdr.sh_size); + if (lseek(fd, shdr.sh_offset, SEEK_SET) < 0) + err(1, "lseek"); + if (read(fd, shstrtab, shdr.sh_size) != shdr.sh_size) + err(1, "read"); + return shstrtab; +} + +/* read string table */ +static char * +read_string_table(int fd, struct elf_section *head, int *strtablen) +{ + char *string_table=NULL; + + while (head) { + if ((strcmp(head->name, ".strtab") == 0 ) + && (head->type == SHT_STRTAB)) { + string_table = malloc(head->size); + if (string_table == NULL) + errx(1, "failed to allocate %lu bytes", + (u_long)head->size); + if (lseek(fd, head->offset, SEEK_SET) < 0) + err(1, "lseek"); + if (read(fd, string_table, head->size) != head->size) + err(1, "read"); + *strtablen = head->size; + break; + } else { + head = head->next; + } + } + return string_table; +} + +static int +read_elf_header(int fd, Elf_Ehdr *ehdr) +{ + ssize_t n; + + n = read(fd, ehdr, sizeof(*ehdr)); + if (n < 0) + err(1, "failed reading %lu bytes", (u_long)sizeof(*ehdr)); + if (n != sizeof(*ehdr)) { + if (debug) + warnx("failed to read %lu bytes", (u_long)sizeof(*ehdr)); + return -1; + } + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0 || + ehdr->e_ident[EI_CLASS] != ELFCLASS) + errx(4, "not in ELF%u format", ELFSIZE); + if (ehdr->e_ehsize != ELF_HDR_SIZE) + errx(4, "file has ELF%u identity, but wrong header size", + ELFSIZE); + + return 0; +} + +/* offset of data segment; this is horrible, but keeps the size of the + module to a minimum */ +static ssize_t data_offset; + +/* return size needed by the module */ +int +elf_mod_sizes(fd, modsize, strtablen, resrvp, sp) + int fd; + size_t *modsize; + int *strtablen; + struct lmc_resrv *resrvp; + struct stat *sp; +{ + Elf_Ehdr ehdr; + ssize_t off = 0; + size_t data_hole = 0; + char *shstrtab, *strtab; + struct elf_section *head, *s, *symtab; + + if (read_elf_header(fd, &ehdr) < 0) + return -1; + shstrtab = read_shstring_table(fd, &ehdr); + read_sections(fd, &ehdr, shstrtab, &head); + + for (s = head; s; s = s->next) { + if ((s->type == SHT_STRTAB) && (s->type == SHT_SYMTAB) + && (s->type == SHT_DYNSYM)) { + continue; + } + if (debug) + fprintf(stderr, + "%s: addr = %p size = %#lx align = %#lx\n", + s->name, s->addr, (u_long)s->size, (u_long)s->align); + /* XXX try to get rid of the hole before the data + section that GNU-ld likes to put there */ + if (strcmp(s->name, ".data") == 0 && s->addr > (void*)off) { + data_offset = roundup(off, s->align); + if (debug) + fprintf(stderr, ".data section forced to " + "offset %p (was %p)\n", + (void*)data_offset, + s->addr); + /* later remove size of compressed hole from off */ + data_hole = (ssize_t)s->addr - data_offset; + } + off = (ssize_t)s->addr + s->size; + } + off -= data_hole; + + /* XXX round to pagesize? */ + *modsize = roundup(off, sysconf(_SC_PAGESIZE)); + free(shstrtab); + + /* get string table length */ + strtab = read_string_table(fd, head, strtablen); + free(strtab); + + /* get symbol table sections */ + get_symtab(&head); + symtab = head; + resrvp->sym_symsize = 0; + while (symtab) { + resrvp->sym_symsize += symtab->size; + symtab = symtab->next; + } + resrvp->sym_size = resrvp->sym_symsize + *strtablen; + free_sections(head); + + return (0); +} + +/* + * Expected linker options: + * + * -R executable to link against + * -e entry point + * -o output file + * -Ttext address to link text segment to in hex (assumes it's + * a page boundry) + * -Tdata address to link data segment to in hex + * <target> object file */ + +#define LINKCMD "ld -R %s -e %s -o %s -Ttext %p %s" +#define LINKCMD2 "ld -R %s -e %s -o %s -Ttext %p -Tdata %p %s" + +/* make a link command; XXX if data_offset above is non-zero, force + data address to be at start of text + offset */ +void +elf_linkcmd(char *buf, + size_t len, + const char *kernel, + const char *entry, + const char *outfile, + const void *address, + const char *object) +{ + ssize_t n; + + if (data_offset == NULL) + n = snprintf(buf, len, LINKCMD, kernel, entry, + outfile, address, object); + else + n = snprintf(buf, len, LINKCMD2, kernel, entry, + outfile, address, + (const char*)address + data_offset, object); + if (n >= len) + errx(1, "link command longer than %lu bytes", (u_long)len); +} + +/* load a prelinked module; returns entry point */ +void * +elf_mod_load(int fd) +{ + Elf_Ehdr ehdr; + size_t zero_size = 0; + size_t b; + ssize_t n; + char *shstrtab; + struct elf_section *head, *s; + char buf[10 * BUFSIZ]; + void *addr = NULL; + + if (read_elf_header(fd, &ehdr) < 0) + return NULL; + + shstrtab = read_shstring_table(fd, &ehdr); + read_sections(fd, &ehdr, shstrtab, &head); + + for (s = head; s; s = s->next) { + if ((s->type != SHT_STRTAB) && (s->type != SHT_SYMTAB) + && (s->type != SHT_DYNSYM)) { + if (debug) + fprintf(stderr, "loading `%s': addr = %p, " + "size = %#lx\n", + s->name, s->addr, (u_long)s->size); + if (s->type == SHT_NOBITS) { + /* skip some space */ + zero_size += s->size; + } else { + if (addr != NULL) { + /* + * if there is a gap in the prelinked + * module, transfer some empty space. + */ + zero_size += (char*)s->addr + - (char*)addr; + } + if (zero_size) { + loadspace(zero_size); + zero_size = 0; + } + b = s->size; + if (lseek(fd, s->offset, SEEK_SET) == -1) + err(1, "lseek"); + while (b) { + n = read(fd, buf, MIN(b, sizeof(buf))); + if (n == 0) + errx(1, "unexpected EOF"); + if (n < 0) + err(1, "read"); + loadbuf(buf, n); + b -= n; + } + addr = (char*)s->addr + s->size; + } + } + } + if (zero_size) + loadspace(zero_size); + + free_sections(head); + free(shstrtab); + return (void*)ehdr.e_entry; +} + +extern int devfd, modfd; + +void +elf_mod_symload(strtablen) + int strtablen; +{ + Elf_Ehdr ehdr; + char *shstrtab; + struct elf_section *head, *s; + char *symbuf, *strbuf; + + /* + * Seek to the text offset to start loading... + */ + if (lseek(modfd, 0, SEEK_SET) == -1) + err(12, "lseek"); + if (read_elf_header(modfd, &ehdr) < 0) + return; + + shstrtab = read_shstring_table(modfd, &ehdr); + read_sections(modfd, &ehdr, shstrtab, &head); + + for (s = head; s; s = s->next) { + struct elf_section *p = s; + + if ((p->type == SHT_SYMTAB) || (p->type == SHT_DYNSYM)) { + if (debug) + fprintf(stderr, "loading `%s': addr = %p, " + "size = %#lx\n", + s->name, s->addr, (u_long)s->size); + /* + * Seek to the file offset to start loading it... + */ + if (lseek(modfd, p->offset, SEEK_SET) == -1) + err(12, "lseek"); + symbuf = malloc(p->size); + if (symbuf == 0) + err(13, "malloc"); + if (read(modfd, symbuf, p->size) != p->size) + err(14, "read"); + + loadsym(symbuf, p->size); + free(symbuf); + } + } + + for (s = head; s; s = s->next) { + struct elf_section *p = s; + + if ((p->type == SHT_STRTAB) + && (strcmp(p->name, ".strtab") == 0 )) { + if (debug) + fprintf(stderr, "loading `%s': addr = %p, " + "size = %#lx\n", + s->name, s->addr, (u_long)s->size); + /* + * Seek to the file offset to start loading it... + */ + if (lseek(modfd, p->offset, SEEK_SET) == -1) + err(12, "lseek"); + strbuf = malloc(p->size); + if (strbuf == 0) + err(13, "malloc"); + if (read(modfd, strbuf, p->size) != p->size) + err(14, "read"); + + loadsym(strbuf, p->size); + free(strbuf); + } + } + + free(shstrtab); + free_sections(head); + return; +} |