1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-25 11:37:56 +00:00
freebsd/sys/boot/common/load_elf_obj.c
Roger Pau Monné ca49b3342d loader: implement multiboot support for Xen Dom0
Implement a subset of the multiboot specification in order to boot Xen
and a FreeBSD Dom0 from the FreeBSD bootloader. This multiboot
implementation is tailored to boot Xen and FreeBSD Dom0, and it will
most surely fail to boot any other multiboot compilant kernel.

In order to detect and boot the Xen microkernel, two new file formats
are added to the bootloader, multiboot and multiboot_obj. Multiboot
support must be tested before regular ELF support, since Xen is a
multiboot kernel that also uses ELF. After a multiboot kernel is
detected, all the other loaded kernels/modules are parsed by the
multiboot_obj format.

The layout of the loaded objects in memory is the following; first the
Xen kernel is loaded as a 32bit ELF into memory (Xen will switch to
long mode by itself), after that the FreeBSD kernel is loaded as a RAW
file (Xen will parse and load it using it's internal ELF loader), and
finally the metadata and the modules are loaded using the native
FreeBSD way. After everything is loaded we jump into Xen's entry point
using a small trampoline. The order of the multiboot modules passed to
Xen is the following, the first module is the RAW FreeBSD kernel, and
the second module is the metadata and the FreeBSD modules.

Since Xen will relocate the memory position of the second
multiboot module (the one that contains the metadata and native
FreeBSD modules), we need to stash the original modulep address inside
of the metadata itself in order to recalculate its position once
booted. This also means the metadata must come before the loaded
modules, so after loading the FreeBSD kernel a portion of memory is
reserved in order to place the metadata before booting.

In order to tell the loader to boot Xen and then the FreeBSD kernel the
following has to be added to the /boot/loader.conf file:

xen_cmdline="dom0_mem=1024M dom0_max_vcpus=2 dom0pvh=1 console=com1,vga"
xen_kernel="/boot/xen"

The first argument contains the command line that will be passed to the Xen
kernel, while the second argument is the path to the Xen kernel itself. This
can also be done manually from the loader command line, by for example
typing the following set of commands:

OK unload
OK load /boot/xen dom0_mem=1024M dom0_max_vcpus=2 dom0pvh=1 console=com1,vga
OK load kernel
OK load zfs
OK load if_tap
OK load ...
OK boot

Sponsored by: Citrix Systems R&D
Reviewed by: jhb
Differential Revision: https://reviews.freebsd.org/D517

For the Forth bits:
Submitted by: Julien Grall <julien.grall AT citrix.com>
2015-01-15 16:27:20 +00:00

537 lines
14 KiB
C

/*-
* Copyright (c) 2004 Ian Dowse <iedowse@freebsd.org>
* Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
* Copyright (c) 1998 Peter Wemm <peter@freebsd.org>
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/exec.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <inttypes.h>
#include <string.h>
#include <machine/elf.h>
#include <stand.h>
#define FREEBSD_ELF
#include <link.h>
#include "bootstrap.h"
#define COPYOUT(s,d,l) archsw.arch_copyout((vm_offset_t)(s), d, l)
#if defined(__i386__) && __ELF_WORD_SIZE == 64
#undef ELF_TARG_CLASS
#undef ELF_TARG_MACH
#define ELF_TARG_CLASS ELFCLASS64
#define ELF_TARG_MACH EM_X86_64
#endif
typedef struct elf_file {
Elf_Ehdr hdr;
Elf_Shdr *e_shdr;
int symtabindex; /* Index of symbol table */
int shstrindex; /* Index of section name string table */
int fd;
vm_offset_t off;
} *elf_file_t;
static int __elfN(obj_loadimage)(struct preloaded_file *mp, elf_file_t ef,
u_int64_t loadaddr);
static int __elfN(obj_lookup_set)(struct preloaded_file *mp, elf_file_t ef,
const char *name, Elf_Addr *startp, Elf_Addr *stopp, int *countp);
static int __elfN(obj_reloc_ptr)(struct preloaded_file *mp, elf_file_t ef,
Elf_Addr p, void *val, size_t len);
static int __elfN(obj_parse_modmetadata)(struct preloaded_file *mp,
elf_file_t ef);
static Elf_Addr __elfN(obj_symaddr)(struct elf_file *ef, Elf_Size symidx);
const char *__elfN(obj_kerneltype) = "elf kernel";
const char *__elfN(obj_moduletype) = "elf obj module";
/*
* Attempt to load the file (file) as an ELF module. It will be stored at
* (dest), and a pointer to a module structure describing the loaded object
* will be saved in (result).
*/
int
__elfN(obj_loadfile)(char *filename, u_int64_t dest,
struct preloaded_file **result)
{
struct preloaded_file *fp, *kfp;
struct elf_file ef;
Elf_Ehdr *hdr;
int err;
ssize_t bytes_read;
fp = NULL;
bzero(&ef, sizeof(struct elf_file));
/*
* Open the image, read and validate the ELF header
*/
if (filename == NULL) /* can't handle nameless */
return(EFTYPE);
if ((ef.fd = open(filename, O_RDONLY)) == -1)
return(errno);
hdr = &ef.hdr;
bytes_read = read(ef.fd, hdr, sizeof(*hdr));
if (bytes_read != sizeof(*hdr)) {
err = EFTYPE; /* could be EIO, but may be small file */
goto oerr;
}
/* Is it ELF? */
if (!IS_ELF(*hdr)) {
err = EFTYPE;
goto oerr;
}
if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS || /* Layout ? */
hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
hdr->e_ident[EI_VERSION] != EV_CURRENT || /* Version ? */
hdr->e_version != EV_CURRENT ||
hdr->e_machine != ELF_TARG_MACH || /* Machine ? */
hdr->e_type != ET_REL) {
err = EFTYPE;
goto oerr;
}
if (hdr->e_shnum * hdr->e_shentsize == 0 || hdr->e_shoff == 0 ||
hdr->e_shentsize != sizeof(Elf_Shdr)) {
err = EFTYPE;
goto oerr;
}
kfp = file_findfile(NULL, __elfN(obj_kerneltype));
if (kfp == NULL) {
printf("elf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadfile: can't load module before kernel\n");
err = EPERM;
goto oerr;
}
if (archsw.arch_loadaddr != NULL)
dest = archsw.arch_loadaddr(LOAD_ELF, hdr, dest);
else
dest = roundup(dest, PAGE_SIZE);
/*
* Ok, we think we should handle this.
*/
fp = file_alloc();
if (fp == NULL) {
printf("elf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadfile: cannot allocate module info\n");
err = EPERM;
goto out;
}
fp->f_name = strdup(filename);
fp->f_type = strdup(__elfN(obj_moduletype));
printf("%s ", filename);
fp->f_size = __elfN(obj_loadimage)(fp, &ef, dest);
if (fp->f_size == 0 || fp->f_addr == 0)
goto ioerr;
/* save exec header as metadata */
file_addmetadata(fp, MODINFOMD_ELFHDR, sizeof(*hdr), hdr);
/* Load OK, return module pointer */
*result = (struct preloaded_file *)fp;
err = 0;
goto out;
ioerr:
err = EIO;
oerr:
file_discard(fp);
out:
close(ef.fd);
if (ef.e_shdr != NULL)
free(ef.e_shdr);
return(err);
}
/*
* With the file (fd) open on the image, and (ehdr) containing
* the Elf header, load the image at (off)
*/
static int
__elfN(obj_loadimage)(struct preloaded_file *fp, elf_file_t ef, u_int64_t off)
{
Elf_Ehdr *hdr;
Elf_Shdr *shdr, *cshdr, *lshdr;
vm_offset_t firstaddr, lastaddr;
int i, nsym, res, ret, shdrbytes, symstrindex;
ret = 0;
firstaddr = lastaddr = (vm_offset_t)off;
hdr = &ef->hdr;
ef->off = (vm_offset_t)off;
/* Read in the section headers. */
shdrbytes = hdr->e_shnum * hdr->e_shentsize;
shdr = alloc_pread(ef->fd, (off_t)hdr->e_shoff, shdrbytes);
if (shdr == NULL) {
printf("\nelf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadimage: read section headers failed\n");
goto out;
}
ef->e_shdr = shdr;
/*
* Decide where to load everything, but don't read it yet.
* We store the load address as a non-zero sh_addr value.
* Start with the code/data and bss.
*/
for (i = 0; i < hdr->e_shnum; i++)
shdr[i].sh_addr = 0;
for (i = 0; i < hdr->e_shnum; i++) {
if (shdr[i].sh_size == 0)
continue;
switch (shdr[i].sh_type) {
case SHT_PROGBITS:
case SHT_NOBITS:
lastaddr = roundup(lastaddr, shdr[i].sh_addralign);
shdr[i].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[i].sh_size;
break;
}
}
/* Symbols. */
nsym = 0;
for (i = 0; i < hdr->e_shnum; i++) {
switch (shdr[i].sh_type) {
case SHT_SYMTAB:
nsym++;
ef->symtabindex = i;
shdr[i].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[i].sh_size;
break;
}
}
if (nsym != 1) {
printf("\nelf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadimage: file has no valid symbol table\n");
goto out;
}
lastaddr = roundup(lastaddr, shdr[ef->symtabindex].sh_addralign);
shdr[ef->symtabindex].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[ef->symtabindex].sh_size;
symstrindex = shdr[ef->symtabindex].sh_link;
if (symstrindex < 0 || symstrindex >= hdr->e_shnum ||
shdr[symstrindex].sh_type != SHT_STRTAB) {
printf("\nelf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadimage: file has invalid symbol strings\n");
goto out;
}
lastaddr = roundup(lastaddr, shdr[symstrindex].sh_addralign);
shdr[symstrindex].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[symstrindex].sh_size;
/* Section names. */
if (hdr->e_shstrndx == 0 || hdr->e_shstrndx >= hdr->e_shnum ||
shdr[hdr->e_shstrndx].sh_type != SHT_STRTAB) {
printf("\nelf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadimage: file has no section names\n");
goto out;
}
ef->shstrindex = hdr->e_shstrndx;
lastaddr = roundup(lastaddr, shdr[ef->shstrindex].sh_addralign);
shdr[ef->shstrindex].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[ef->shstrindex].sh_size;
/* Relocation tables. */
for (i = 0; i < hdr->e_shnum; i++) {
switch (shdr[i].sh_type) {
case SHT_REL:
case SHT_RELA:
lastaddr = roundup(lastaddr, shdr[i].sh_addralign);
shdr[i].sh_addr = (Elf_Addr)lastaddr;
lastaddr += shdr[i].sh_size;
break;
}
}
/* Clear the whole area, including bss regions. */
kern_bzero(firstaddr, lastaddr - firstaddr);
/* Figure section with the lowest file offset we haven't loaded yet. */
for (cshdr = NULL; /* none */; /* none */)
{
/*
* Find next section to load. The complexity of this loop is
* O(n^2), but with the number of sections being typically
* small, we do not care.
*/
lshdr = cshdr;
for (i = 0; i < hdr->e_shnum; i++) {
if (shdr[i].sh_addr == 0 ||
shdr[i].sh_type == SHT_NOBITS)
continue;
/* Skip sections that were loaded already. */
if (lshdr != NULL &&
lshdr->sh_offset >= shdr[i].sh_offset)
continue;
/* Find section with smallest offset. */
if (cshdr == lshdr ||
cshdr->sh_offset > shdr[i].sh_offset)
cshdr = &shdr[i];
}
if (cshdr == lshdr)
break;
if (kern_pread(ef->fd, (vm_offset_t)cshdr->sh_addr,
cshdr->sh_size, (off_t)cshdr->sh_offset) != 0) {
printf("\nelf" __XSTRING(__ELF_WORD_SIZE)
"_obj_loadimage: read failed\n");
goto out;
}
}
file_addmetadata(fp, MODINFOMD_SHDR, shdrbytes, shdr);
res = __elfN(obj_parse_modmetadata)(fp, ef);
if (res != 0)
goto out;
ret = lastaddr - firstaddr;
fp->f_addr = firstaddr;
printf("size 0x%lx at 0x%lx", (u_long)ret, (u_long)firstaddr);
out:
printf("\n");
return ret;
}
#if defined(__i386__) && __ELF_WORD_SIZE == 64
struct mod_metadata64 {
int md_version; /* structure version MDTV_* */
int md_type; /* type of entry MDT_* */
u_int64_t md_data; /* specific data */
u_int64_t md_cval; /* common string label */
};
#endif
int
__elfN(obj_parse_modmetadata)(struct preloaded_file *fp, elf_file_t ef)
{
struct mod_metadata md;
#if defined(__i386__) && __ELF_WORD_SIZE == 64
struct mod_metadata64 md64;
#endif
struct mod_depend *mdepend;
struct mod_version mver;
char *s;
int error, modcnt, minfolen;
Elf_Addr v, p, p_stop;
if (__elfN(obj_lookup_set)(fp, ef, "modmetadata_set", &p, &p_stop,
&modcnt) != 0)
return 0;
modcnt = 0;
while (p < p_stop) {
COPYOUT(p, &v, sizeof(v));
error = __elfN(obj_reloc_ptr)(fp, ef, p, &v, sizeof(v));
if (error != 0)
return (error);
#if defined(__i386__) && __ELF_WORD_SIZE == 64
COPYOUT(v, &md64, sizeof(md64));
error = __elfN(obj_reloc_ptr)(fp, ef, v, &md64, sizeof(md64));
if (error != 0)
return (error);
md.md_version = md64.md_version;
md.md_type = md64.md_type;
md.md_cval = (const char *)(uintptr_t)md64.md_cval;
md.md_data = (void *)(uintptr_t)md64.md_data;
#else
COPYOUT(v, &md, sizeof(md));
error = __elfN(obj_reloc_ptr)(fp, ef, v, &md, sizeof(md));
if (error != 0)
return (error);
#endif
p += sizeof(Elf_Addr);
switch(md.md_type) {
case MDT_DEPEND:
s = strdupout((vm_offset_t)md.md_cval);
minfolen = sizeof(*mdepend) + strlen(s) + 1;
mdepend = malloc(minfolen);
if (mdepend == NULL)
return ENOMEM;
COPYOUT((vm_offset_t)md.md_data, mdepend,
sizeof(*mdepend));
strcpy((char*)(mdepend + 1), s);
free(s);
file_addmetadata(fp, MODINFOMD_DEPLIST, minfolen,
mdepend);
free(mdepend);
break;
case MDT_VERSION:
s = strdupout((vm_offset_t)md.md_cval);
COPYOUT((vm_offset_t)md.md_data, &mver, sizeof(mver));
file_addmodule(fp, s, mver.mv_version, NULL);
free(s);
modcnt++;
break;
case MDT_MODULE:
case MDT_PNP_INFO:
break;
default:
printf("unknown type %d\n", md.md_type);
break;
}
}
return 0;
}
static int
__elfN(obj_lookup_set)(struct preloaded_file *fp, elf_file_t ef,
const char* name, Elf_Addr *startp, Elf_Addr *stopp, int *countp)
{
Elf_Ehdr *hdr;
Elf_Shdr *shdr;
char *p;
vm_offset_t shstrtab;
int i;
hdr = &ef->hdr;
shdr = ef->e_shdr;
shstrtab = shdr[ef->shstrindex].sh_addr;
for (i = 0; i < hdr->e_shnum; i++) {
if (shdr[i].sh_type != SHT_PROGBITS)
continue;
if (shdr[i].sh_name == 0)
continue;
p = strdupout(shstrtab + shdr[i].sh_name);
if (strncmp(p, "set_", 4) == 0 && strcmp(p + 4, name) == 0) {
*startp = shdr[i].sh_addr;
*stopp = shdr[i].sh_addr + shdr[i].sh_size;
*countp = (*stopp - *startp) / sizeof(Elf_Addr);
free(p);
return (0);
}
free(p);
}
return (ESRCH);
}
/*
* Apply any intra-module relocations to the value. p is the load address
* of the value and val/len is the value to be modified. This does NOT modify
* the image in-place, because this is done by kern_linker later on.
*/
static int
__elfN(obj_reloc_ptr)(struct preloaded_file *mp, elf_file_t ef, Elf_Addr p,
void *val, size_t len)
{
Elf_Ehdr *hdr;
Elf_Shdr *shdr;
Elf_Addr off = p;
Elf_Addr base;
Elf_Rela a, *abase;
Elf_Rel r, *rbase;
int error, i, j, nrel, nrela;
hdr = &ef->hdr;
shdr = ef->e_shdr;
for (i = 0; i < hdr->e_shnum; i++) {
if (shdr[i].sh_type != SHT_RELA && shdr[i].sh_type != SHT_REL)
continue;
base = shdr[shdr[i].sh_info].sh_addr;
if (base == 0 || shdr[i].sh_addr == 0)
continue;
if (off < base || off + len > base +
shdr[shdr[i].sh_info].sh_size)
continue;
switch (shdr[i].sh_type) {
case SHT_RELA:
abase = (Elf_Rela *)(intptr_t)shdr[i].sh_addr;
nrela = shdr[i].sh_size / sizeof(Elf_Rela);
for (j = 0; j < nrela; j++) {
COPYOUT(abase + j, &a, sizeof(a));
error = __elfN(reloc)(ef, __elfN(obj_symaddr),
&a, ELF_RELOC_RELA, base, off, val, len);
if (error != 0)
return (error);
}
break;
case SHT_REL:
rbase = (Elf_Rel *)(intptr_t)shdr[i].sh_addr;
nrel = shdr[i].sh_size / sizeof(Elf_Rel);
for (j = 0; j < nrel; j++) {
COPYOUT(rbase + j, &r, sizeof(r));
error = __elfN(reloc)(ef, __elfN(obj_symaddr),
&r, ELF_RELOC_REL, base, off, val, len);
if (error != 0)
return (error);
}
break;
}
}
return (0);
}
/* Look up the address of a specified symbol. */
static Elf_Addr
__elfN(obj_symaddr)(struct elf_file *ef, Elf_Size symidx)
{
Elf_Sym sym;
Elf_Addr base;
int symcnt;
symcnt = ef->e_shdr[ef->symtabindex].sh_size / sizeof(Elf_Sym);
if (symidx >= symcnt)
return (0);
COPYOUT(ef->e_shdr[ef->symtabindex].sh_addr + symidx * sizeof(Elf_Sym),
&sym, sizeof(sym));
if (sym.st_shndx == SHN_UNDEF || sym.st_shndx >= ef->hdr.e_shnum)
return (0);
base = ef->e_shdr[sym.st_shndx].sh_addr;
if (base == 0)
return (0);
return (base + sym.st_value);
}