aos/tools/armv8_bootimage/armv8_bootimage.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

637 lines
20 KiB
C

/*
* Create a blob with a Multiboot2 image for the ARMv8 platform
*
* This tool reads menu.lst, loads a boot driver, a CPU kernel and modules,
* and assemble them into a Multiboot2 image. Adds also relocation info for
* the driver and the kernel.
*
* Copyright (c) 2016, ETH Zurich.
* All rights reserved.
*
* This file is distributed under the terms in the attached LICENSE file.
* If you do not find this file, copies can be found by writing to:
* ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libelf.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* We need to be able to parse menu.lst files, create multiboot images. */
#include "../../include/grubmenu.h"
#include "../../include/multiboot2.h"
#include "blob.h"
#define DBG(format, ...) printf(format, ## __VA_ARGS__)
/* Keep physical addresses and kernel virtual addresses separated, as far as
* possible. */
typedef uint64_t kvaddr_t;
typedef uint64_t paddr_t;
/*** A Linear Memory Allocator ***/
static paddr_t phys_alloc_start = 0;
static size_t round_up(size_t x, size_t y)
{
size_t z = x + (y - 1);
return z - (z % y);
}
/* Advance the allocator to an address with the given alignment. */
static paddr_t align_alloc(paddr_t align)
{
phys_alloc_start = round_up(phys_alloc_start, align);
return phys_alloc_start;
}
/* Allocate an aligned block. */
static paddr_t phys_alloc(size_t size, size_t align)
{
align_alloc(align);
paddr_t addr = phys_alloc_start;
phys_alloc_start += size;
return addr;
}
/*** Failure Handling ***/
static void fail(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
static void fail_errno(const char *fmt, ...)
{
char s[1024];
va_list ap;
va_start(ap, fmt);
vsnprintf(s, 1024, fmt, ap);
va_end(ap);
perror(s);
exit(EXIT_FAILURE);
}
static void fail_elf(const char *s)
{
fprintf(stderr, "%s: %s\n", s, elf_errmsg(elf_errno()));
exit(EXIT_FAILURE);
}
static void join_paths(char *dst, const char *src1, const char *src2)
{
strcpy(dst, src1);
dst[strlen(src1)] = '/';
strcpy(dst + strlen(src1) + 1, src2);
}
struct ram_region {
uint64_t base;
uint64_t npages;
void *buffer;
};
struct loaded_module {
void *data;
paddr_t paddr;
size_t len, size;
const char *shortname;
};
struct loaded_image {
struct ram_region segment;
size_t loaded_size;
paddr_t loaded_paddr;
kvaddr_t loaded_vaddr;
paddr_t entry;
const char *extrasym_name;
void *extrasym_ptr;
void *shdrs, *symtab, *strtab, *shstrtab;
size_t shdrs_size, symtab_size, strtab_size, shstrtab_size;
size_t shdrs_entsize, symtab_entsize;
unsigned no_relocations;
struct Blob_relocation *relocations;
};
/* Load an ELF file as a raw data blob. */
void raw_load(const char *path, struct loaded_module *m)
{
struct stat mstat;
if (stat(path, &mstat))
fail_errno("stat: %s", path);
size_t data_len = mstat.st_size;
m->len = round_up(data_len, BASE_PAGE_SIZE);
m->size = data_len;
m->data = calloc(m->len, 1);
if (!m->data)
fail_errno("calloc");
m->paddr = phys_alloc(m->len, BASE_PAGE_SIZE);
printf("Allocated 0x%zx at PA %016zx for %s (%zd)\n", m->len, m->paddr,
path, data_len);
int fd = open(path, O_RDONLY);
if (fd < 0)
fail_errno("open: %s", path);
size_t read_len = read(fd, m->data, data_len);
if (read_len != data_len)
fail_errno("fread");
close(fd);
}
/*** Multiboot ***/
#define ROUND_UP(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
#define ALIGN(x) ROUND_UP((x), sizeof(uintptr_t))
/* Create the multiboot header, using only *physical* addresses. */
void *create_multiboot_info(struct menu_lst *menu,
struct loaded_module *modules,
size_t * mb_size, paddr_t * mb_base,
paddr_t entry)
{
size_t size;
unsigned i;
void *cursor;
/* Calculate the boot information size. */
/* Multiboot2 information data structure */
size = 8;
/* cpu driver command line */
size += ALIGN(sizeof(struct multiboot_tag_string)
+ strlen(menu->kernel.args) + 1);
// /* Boot driver module tag, including command line and ELF image */
size += ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->boot_driver.path) + 2);
// /* CPU driver module tag, including command line and ELF image */
size += ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->kernel.path) + strlen(menu->kernel.args) +
2);
/* All other modules */
for (i = 0; i < menu->nmodules; i++) {
size += ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->modules[i].path) +
strlen(menu->modules[i].args) + 2);
}
#define MEM_MAP_SIZE (1<<13)
/* EFI memory map */
size += ALIGN(sizeof(struct multiboot_tag_efi_mmap) + MEM_MAP_SIZE);
// END tag
size += ALIGN(sizeof(struct multiboot_tag));
size_t allocated_size = round_up(size, BASE_PAGE_SIZE);
/* Allocate target addresses. */
paddr_t base = phys_alloc(size, BASE_PAGE_SIZE);
*mb_size = allocated_size;
*mb_base = base;
/* Allocate our host buffer. */
void *mb = calloc(allocated_size, 1);
if (!mb)
fail_errno("calloc");
cursor = mb;
/* Skip the information structure for now */
cursor += 8;
/* Add the boot command line */
{
struct multiboot_tag_string *bootcmd =
(struct multiboot_tag_string *) cursor;
bootcmd->type = MULTIBOOT_TAG_TYPE_CMDLINE;
bootcmd->size = ALIGN(sizeof(struct multiboot_tag_string)
+ strlen(menu->kernel.path) +
strlen(menu->kernel.args) + 2);
sprintf(bootcmd->string, "%s %s", menu->kernel.path,
menu->kernel.args);
cursor += bootcmd->size;
}
/* Add the boot driver module. */
{
struct multiboot_tag_module_64 *boot_driver =
(struct multiboot_tag_module_64 *) cursor;
boot_driver->type = MULTIBOOT_TAG_TYPE_MODULE_64;
boot_driver->size = ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->boot_driver.path) + 2);
boot_driver->mod_start = (multiboot_uint64_t) modules[0].paddr;
boot_driver->mod_end =
(multiboot_uint64_t) (modules[0].paddr + modules[0].size - 1);
sprintf(boot_driver->cmdline, "%s", menu->boot_driver.path);
cursor += boot_driver->size;
}
/* Add the kernel module. */
{
struct multiboot_tag_module_64 *kernel =
(struct multiboot_tag_module_64 *) cursor;
kernel->type = MULTIBOOT_TAG_TYPE_MODULE_64;
kernel->size = ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->kernel.path) +
strlen(menu->kernel.args) + 2);
kernel->mod_start = (multiboot_uint64_t) modules[1].paddr;
kernel->mod_end =
(multiboot_uint64_t) (modules[1].paddr + modules[1].size - 1);
sprintf(kernel->cmdline, "%s %s", menu->kernel.path,
menu->kernel.args);
cursor += kernel->size;
}
/* Add the remaining modules */
for (i = 0; i < menu->nmodules; i++) {
struct multiboot_tag_module_64 *module =
(struct multiboot_tag_module_64 *) cursor;
module->type = MULTIBOOT_TAG_TYPE_MODULE_64;
module->size = ALIGN(sizeof(struct multiboot_tag_module_64)
+ strlen(menu->modules[i].path) +
strlen(menu->modules[i].args) + 2);
module->mod_start = (multiboot_uint64_t) modules[i + 2].paddr;
module->mod_end =
(multiboot_uint64_t) (modules[i + 2].paddr +
modules[i + 2].size - 1);
sprintf(module->cmdline, "%s %s", menu->modules[i].path,
menu->modules[i].args);
cursor += module->size;
}
/* Add the EFI MMAP tag */
{
struct multiboot_tag_efi_mmap *mmap_tag =
(struct multiboot_tag_efi_mmap *) cursor;
mmap_tag->type = MULTIBOOT_TAG_TYPE_EFI_MMAP;
cursor += sizeof(struct multiboot_tag_efi_mmap);
}
return mb;
}
int relocate_elf(struct ram_region *segment, Elf * elf,
Elf64_Phdr * phdr, size_t phnum, size_t shnum,
unsigned *no_relocations,
struct Blob_relocation **relocations)
{
size_t i;
*no_relocations = 0;
/* Search for relocaton sections. */
for (i = 0; i < shnum; i++) {
Elf_Scn *scn = elf_getscn(elf, i);
if (!scn) {
printf("elf_getscn: %s\n", elf_errmsg(elf_errno()));
return -1;
}
Elf64_Shdr *shdr = elf64_getshdr(scn);
if (!shdr) {
printf("elf64_getshdr: %s\n", elf_errmsg(elf_errno()));
return -1;
}
if (shdr->sh_type == SHT_DYNAMIC) {
int relocations_size;
Elf_Data *data = elf_getdata(scn, NULL);
Elf64_Dyn *dt = (Elf64_Dyn *) data->d_buf;
for (; dt->d_tag && dt->d_tag != DT_RELACOUNT; dt++) {
}
assert(dt->d_tag == DT_RELACOUNT);
*no_relocations = dt->d_un.d_val;
relocations_size =
round_up(*no_relocations * sizeof(struct Blob_relocation),
BASE_PAGE_SIZE);
*relocations = malloc(relocations_size);
} else if (shdr->sh_type == SHT_RELA) {
if (shdr->sh_info != 0) {
printf("I expected global relocations, but got"
" section-specific ones.\n");
return -1;
}
/* Hardcoded for one loadable segment.
XXX: seems to be not always the case for some ARMv8 builids.
*/
//ASSERT(phnum == 1);
Elf64_Addr segment_elf_base = phdr[0].p_vaddr;
Elf64_Addr segment_load_base = segment->base;
Elf64_Sxword segment_delta =
segment_load_base - segment_elf_base;
/* Walk the section data descriptors. */
Elf_Data *reldata;
for (reldata = elf_getdata(scn, NULL);
reldata; reldata = elf_getdata(scn, reldata)) {
size_t rsize;
if (shdr->sh_type == SHT_REL)
rsize = sizeof(Elf64_Rel);
else
rsize = sizeof(Elf64_Rela);
size_t nrel = reldata->d_size / rsize;
/* Iterate through the relocations. */
size_t i;
for (i = 0; i < nrel; i++) {
void *reladdr = reldata->d_buf + i * rsize;
Elf64_Addr offset;
Elf64_Xword sym, type;
Elf64_Sxword addend;
assert(shdr->sh_type == SHT_RELA);
Elf64_Rela *rel = reladdr;
offset = rel->r_offset;
sym = ELF64_R_SYM(rel->r_info);
type = ELF64_R_TYPE(rel->r_info);
addend = rel->r_addend;
assert(type == R_AARCH64_RELATIVE);
if (sym != 0) {
printf("Relocation references a"
" dynamic symbol, which is"
" unsupported.\n");
return -1;
}
/* Delta(S) + A */
(*relocations)[i].offset = offset;
(*relocations)[i].addend = addend;
}
}
}
}
return 0;
}
/* Load and relocate an ELF, with the given offset between the physical
* address at which it is loaded, and the virtual address at which it
* executes. For the boot driver, the offset is zero. Return a variety of
* information about the loaded image. */
static void load(struct loaded_module *module, uint32_t vp_offset,
struct loaded_image *image, int save_sections)
{
int i;
/* Open the ELF. */
Elf *elf = elf_memory(module->data, module->size);
if (!elf)
fail_elf("elf_begin");
/* Grab the unrelocated entry address from the header. */
Elf64_Ehdr *ehdr = elf64_getehdr(elf);
if (!ehdr)
fail_elf("elf64_getehdr");
image->entry = ehdr->e_entry;
/* Grab the program headers i.e. the list of loadable segments. */
size_t phnum;
if (elf_getphdrnum(elf, &phnum))
fail_elf("elf_getphnum");
Elf64_Phdr *phdr = elf64_getphdr(elf);
if (!phdr)
fail_elf("elf_getphdr");
DBG("%zd program segments.\n", phnum);
/* Grab the raw ELF data. */
size_t elfsize;
void *elfdata = elf_rawfile(elf, &elfsize);
if (!elfdata)
fail_elf("elf_rawfile");
/* Count the loadable segments, to allocate the region list. */
size_t nloadsegs = 0;
for (i = 0; i < phnum; i++) {
if (phdr[i].p_type == PT_LOAD)
nloadsegs++;
}
for (i = 0; i < phnum; i++) {
printf
("Segment %d load address %zx, offset %zx, file size %zx, memory size %zx\n",
i, phdr[i].p_vaddr, phdr[i].p_offset, phdr[i].p_filesz,
phdr[i].p_memsz);
if (phdr[i].p_type != PT_LOAD)
continue;
unsigned p_pages =
round_up(phdr[i].p_memsz, BASE_PAGE_SIZE) / BASE_PAGE_SIZE;
void *p_buf;
paddr_t pa = phys_alloc(phdr[i].p_memsz, BASE_PAGE_SIZE);
p_buf = calloc(p_pages * BASE_PAGE_SIZE, 1);
assert(p_buf);
image->segment.buffer = p_buf;
image->segment.base = pa;
image->segment.npages = p_pages;
memcpy(p_buf, module->data + phdr[i].p_offset, phdr[i].p_filesz);
}
size_t shnum;
int status;
status = elf_getshdrnum(elf, &shnum);
if (status) {
printf("elf_getshdrnum: %s\n", elf_errmsg(elf_errno()));
assert(0);
}
status =
relocate_elf(&image->segment, elf, phdr, phnum, shnum,
&image->no_relocations, &image->relocations);
if (status) {
printf("Relocation failed.\n");
assert(0);
}
elf_end(elf);
}
int main(int argc, char *argv[])
{
char pathbuf[PATH_MAX + 1];
// if(argc != 6) usage(argv[0]);
const char *menu_lst = argv[1],
*outfile = argv[2], *buildroot = argv[3];
errno = 0;
printf("ARMv8 Static Bootloader\n");
/* Read the menu.lst file. */
printf("Reading boot configuration from %s\n", menu_lst);
struct menu_lst *menu = read_menu_lst(menu_lst);
struct loaded_module *modules =
calloc(menu->nmodules + 2, sizeof(struct loaded_module));
if (!modules)
fail_errno("calloc");
// create the Blob
paddr_t base = phys_alloc(sizeof(struct Blob), BASE_PAGE_SIZE);
printf("Blob info struct at PA %016lx\n", base);
// Load the boot driver
join_paths(pathbuf, buildroot, menu->boot_driver.path);
raw_load(pathbuf, modules);
/* Use the filename as a short identifier. */
const char *lastslash = strrchr(menu->boot_driver.path, '/');
if (lastslash) {
modules[0].shortname = lastslash + 1;
} else {
modules[0].shortname = "";
}
// Load the kernel
join_paths(pathbuf, buildroot, menu->kernel.path);
raw_load(pathbuf, modules + 1);
/* Use the filename as a short identifier. */
lastslash = strrchr(menu->kernel.path, '/');
if (lastslash) {
modules[1].shortname = lastslash + 1;
} else {
modules[1].shortname = "";
}
/*** Load the modules. ***/
for (size_t i = 0; i < menu->nmodules; i++) {
join_paths(pathbuf, buildroot, menu->modules[i].path);
raw_load(pathbuf, modules + i + 2);
/* Use the filename as a short identifier. */
lastslash = strrchr(menu->modules[i].path, '/');
if (lastslash) {
modules[i + 2].shortname = lastslash + 1;
} else {
modules[i + 2].shortname = "";
}
}
if (elf_version(EV_CURRENT) == EV_NONE)
fail("ELF library version out of date.\n");
/*** Load the boot driver. ***/
/* Load and relocate it. */
struct loaded_image bd_image[2];
bd_image[0].extrasym_name = "boot_arguments";
load(modules, 0, bd_image, 1);
load(modules + 1, 0, bd_image + 1, 1);
printf("Boot driver entry point: PA %08zx\n", bd_image[0].entry);
printf("CPU driver entry point: PA %08zx\n", bd_image[1].entry);
paddr_t pa, endpa;
struct Blob blob;
memset(blob.data, 0, sizeof(blob.data));
blob.magic = 0x12345678fedcba90;
pa = phys_alloc(bd_image[0].no_relocations *
sizeof(struct Blob_relocation), BASE_PAGE_SIZE);
printf("Boot relocations PA %016zx,%d\n", pa,
bd_image[0].no_relocations);
blob.boot_driver_relocations = pa;
blob.boot_driver_relocations_count = bd_image[0].no_relocations;
blob.boot_driver_segment = bd_image[0].segment.base;
blob.boot_driver_segment_size = bd_image[0].segment.npages * BASE_PAGE_SIZE;
blob.boot_driver_entry = (uint64_t)bd_image[0].entry;
pa = phys_alloc(bd_image[1].no_relocations *
sizeof(struct Blob_relocation), BASE_PAGE_SIZE);
printf("Kernel relocations PA %016zx,%d\n", pa,
bd_image[1].no_relocations);
blob.cpu_driver_relocations = pa;
blob.cpu_driver_relocations_count = bd_image[1].no_relocations;
blob.cpu_driver_segment = bd_image[1].segment.base;
blob.cpu_driver_segment_size = bd_image[1].segment.npages * BASE_PAGE_SIZE;
blob.cpu_driver_entry = (uint64_t)bd_image[1].entry;
/*** Create the multiboot info header. ***/
size_t mb_size, size;
paddr_t mb_base;
void *mb_image =
create_multiboot_info(menu, modules, &mb_size, &mb_base,
bd_image[1].entry);
endpa = phys_alloc(BASE_PAGE_SIZE, BASE_PAGE_SIZE);
printf("Final PA %016zx\n", endpa);
blob.multiboot = mb_base;
blob.multiboot_size = mb_size;
blob.modules = modules[0].paddr;
for (size_t i = 0; i < menu->nmodules + 2; i++) {
blob.modules_size += modules[i].len;
}
size_t r;
FILE *fp = fopen(outfile, "wb");
assert(fp >= 0);
// write the blob info
r = fwrite(&blob, 1, BASE_PAGE_SIZE, fp);
assert(r == BASE_PAGE_SIZE);
// write the modules
for (size_t i = 0; i < menu->nmodules + 2; i++) {
r = fwrite(modules[i].data, 1, modules[i].len, fp);
assert(r == modules[i].len);
}
// write the boot driver's ELF section
r = fwrite(bd_image[0].segment.buffer, 1,
bd_image[0].segment.npages * BASE_PAGE_SIZE, fp);
assert(r == bd_image[0].segment.npages * BASE_PAGE_SIZE);
// write the kernel's ELF section
r = fwrite(bd_image[1].segment.buffer, 1,
bd_image[1].segment.npages * BASE_PAGE_SIZE, fp);
assert(r == bd_image[1].segment.npages * BASE_PAGE_SIZE);
// write the boot driver's relocations
size =
round_up(bd_image[0].no_relocations *
sizeof(struct Blob_relocation), BASE_PAGE_SIZE);
r = fwrite(bd_image[0].relocations, 1, size, fp);
assert(r == size);
// write the kernel's relocations
size =
round_up(bd_image[1].no_relocations *
sizeof(struct Blob_relocation), BASE_PAGE_SIZE);
r = fwrite(bd_image[1].relocations, 1, size, fp);
assert(r == size);
// write the multiboot info
r = fwrite(mb_image, 1, mb_size, fp);
assert(r == mb_size);
fclose(fp);
return 0;
}