firmware: add simple ELF loader for static binaries

This is a small & simple ELF loader which just copies
program segments from disk to memory. It only supports
static binaries right now.

Change-Id: I8944feb5b9dafcc5c56e12383aed25e2718ad7ea
This commit is contained in:
Aidan MacDonald 2026-01-04 00:00:05 +00:00 • committed by Solomon Peachy
parent 21ba79d431
commit a610998ea4
5 changed files with 466 additions and 0 deletions

View file

@ -58,6 +58,10 @@ target/hosted/rolo.c
lc-rock.c
#endif
#if defined(HAVE_ELF)
elf_loader.c
#endif
#if defined(HAVE_BOOTDATA) || defined(HAVE_MULTIBOOT)
common/multiboot.c
#ifndef BOOTLOADER

175
firmware/elf_loader.c Normal file
View file

@ -0,0 +1,175 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2026 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "elf_loader.h"
#include "elf-target.h"
#include "file.h"
#if defined(ROCKBOX_LITTLE_ENDIAN)
# define NATIVE_ELF_DATA ELFDATA2LSB
#elif defined(ROCKBOX_BIG_ENDIAN)
# define NATIVE_ELF_DATA ELFDATA2MSB
#else
# error "Unknown endianness!"
#endif
_Static_assert(TARGET_ELF_DATA == NATIVE_ELF_DATA,
"ELF binaries with foreign endianness not supported");
static int elf_validate_header(const struct elf_header *header)
{
if (header->ei_magic[0] != ELFMAG0 ||
header->ei_magic[1] != ELFMAG1 ||
header->ei_magic[2] != ELFMAG2 ||
header->ei_magic[3] != ELFMAG3)
return ELF_ERR_BADMAGIC;
if (header->ei_class != TARGET_ELF_CLASS ||
header->ei_data != TARGET_ELF_DATA)
return ELF_ERR_UNSUPP_ARCH;
if (header->ei_version != EV_CURRENT ||
header->e_version != EV_CURRENT)
return ELF_ERR_UNSUPP_VERSION;
if (header->ei_osabi != TARGET_ELF_OSABI ||
header->ei_abiversion != TARGET_ELF_ABIVERSION)
return ELF_ERR_UNSUPP_OS;
if (header->e_type != ET_EXEC)
return ELF_ERR_UNSUPP_TYPE;
if (header->e_machine != TARGET_ELF_MACHINE)
return ELF_ERR_UNSUPP_ARCH;
if (header->e_phoff == 0)
return ELF_ERR_EMPTY;
if (header->e_phentsize < sizeof(struct elf_phdr))
return ELF_ERR_UNSUPP_PHSIZE;
if (header->e_phnum == PN_XNUM)
return ELF_ERR_TOO_MANY_PHDRS;
return ELF_OK;
}
static int elf_validate_phdr(const struct elf_phdr *phdr,
const struct elf_load_context *ctx)
{
if (phdr->p_type != PT_LOAD)
return ELF_ERR_UNSUPP_PHDR;
if (phdr->p_filesz > phdr->p_memsz)
return ELF_ERR_INVALID_PHDR;
/* Ignore headers which occupy zero memory */
if (phdr->p_memsz == 0)
return 0;
for (size_t i = 0; i < ctx->num_mmap; ++i)
{
const struct elf_memory_map *entry = &ctx->mmap[i];
if (phdr->p_vaddr >= entry->addr &&
phdr->p_vaddr < entry->addr + entry->size &&
phdr->p_memsz <= entry->size)
{
if ((phdr->p_flags & entry->flags) != phdr->p_flags)
return ELF_ERR_MEM_INCOMPAT_FLAGS;
return ELF_OK;
}
}
return ELF_ERR_MEM_UNMAPPED;
}
int elf_loadfd(int fd,
const struct elf_load_context *ctx,
void **entrypoint)
{
struct elf_header ehdr;
struct elf_phdr phdr;
ssize_t nread;
int err;
nread = read(fd, &ehdr, sizeof(ehdr));
if (nread != (ssize_t)sizeof(ehdr))
return ELF_ERR_IO;
err = elf_validate_header(&ehdr);
if (err != ELF_OK)
return err;
/*
* Iterate over program headers, copying segments to memory
*/
for (size_t ph_index = 0; ph_index < ehdr.e_phnum; ++ph_index)
{
off_t ph_off = ehdr.e_phoff + (ph_index * ehdr.e_phentsize);
if (lseek(fd, ph_off, SEEK_SET) == (off_t)-1)
return ELF_ERR_IO;
nread = read(fd, &phdr, sizeof(phdr));
if (nread != (ssize_t)sizeof(phdr))
return ELF_ERR_IO;
err = elf_validate_phdr(&phdr, ctx);
if (err)
return err;
/* Load file data to memory if needed */
if (phdr.p_filesz > 0)
{
if (lseek(fd, phdr.p_offset, SEEK_SET) == (off_t)-1)
return ELF_ERR_IO;
nread = read(fd, (void *)phdr.p_vaddr, phdr.p_filesz);
if (nread < 0 || (uint32_t)nread != phdr.p_filesz)
return ELF_ERR_IO;
}
/* Zero out any memory not backed by file data */
if (phdr.p_memsz > phdr.p_filesz)
{
void *addr = (void *)phdr.p_vaddr + phdr.p_filesz;
size_t size = phdr.p_memsz - phdr.p_filesz;
memset(addr, 0, size);
}
}
*entrypoint = (void *)ehdr.e_entry;
return ELF_OK;
}
int elf_loadpath(const char *filename,
const struct elf_load_context *ctx,
void **entrypoint)
{
int fd = open(filename, O_RDONLY);
if (fd < 0)
return ELF_ERR_FILE_NOT_FOUND;
int err = elf_loadfd(fd, ctx, entrypoint);
close(fd);
return err;
}

View file

@ -0,0 +1,50 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2026 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __ELF_TARGET_H__
#define __ELF_TARGET_H__
#include "elf.h"
#include "config.h"
#define TARGET_ELF_CLASS ELFCLASS32
#define TARGET_ELF_OSABI ELFOSABI_SYSV
#define TARGET_ELF_ABIVERSION 0
#define elf_phdr elf32_phdr
#if defined(CPU_ARM)
# define TARGET_ELF_MACHINE EM_ARM
#elif defined(CPU_MIPS)
# define TARGET_ELF_MACHINE EM_MIPS
#elif defined(CPU_COLDFIRE)
# define TARGET_ELF_MACHINE EM_68K
#else
# error "Unknown CPU architecture!"
#endif
#if defined(ROCKBOX_LITTLE_ENDIAN)
# define TARGET_ELF_DATA ELFDATA2LSB
#elif defined(ROCKBOX_BIG_ENDIAN)
# define TARGET_ELF_DATA ELFDATA2MSB
#else
# error "Unknown endianness!"
#endif
#endif /* __ELF_TARGET_H__ */

143
firmware/include/elf.h Normal file
View file

@ -0,0 +1,143 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2026 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __ELF_H__
#define __ELF_H__
#include <stdint.h>
/* Field offsets in the e_ident array */
#define EI_MAG0 0
#define EI_MAG1 1
#define EI_MAG2 2
#define EI_MAG3 3
#define EI_CLASS 4
#define EI_DATA 5
#define EI_VERSION 6
#define EI_OSABI 7
#define EI_ABIVERSION 8
#define EI_PAD 9
#define EI_NIDENT 16
/* Values for ei_magic */
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'
/* Values for ei_class */
#define ELFCLASSNONE 0
#define ELFCLASS32 1
#define ELFCLASS64 2
/* Values for ei_data */
#define ELFDATANONE 0
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2
/* Values for ei_version and e_version */
#define EV_NONE 0
#define EV_CURRENT 1
/* Values for ei_osabi */
#define ELFOSABI_NONE ELFOSABI_SYSV
#define ELFOSABI_SYSV 0
/* Values for e_type */
#define ET_NONE 0
#define ET_REL 1
#define ET_EXEC 2
#define ET_DYN 3
#define ET_CORE 4
/* Values for e_machine */
#define EM_NONE 0
#define EM_68K 4
#define EM_MIPS 8
#define EM_ARM 40
/*
* Special value for e_phnum when the real number of
* program headers is too large.
*/
#define PN_XNUM 0xFFFFu
/* ELF basic types */
typedef uint32_t elf_addr_t;
typedef uint32_t elf_off_t;
/* ELF file header */
struct elf_header
{
union {
uint8_t e_ident[EI_NIDENT];
struct {
uint8_t ei_magic[4];
uint8_t ei_class;
uint8_t ei_data;
uint8_t ei_version;
uint8_t ei_osabi;
uint8_t ei_abiversion;
};
};
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
elf_addr_t e_entry;
elf_off_t e_phoff;
elf_off_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
};
/* Values for p_type */
#define PT_NULL 0
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
#define PT_NOTE 4
#define PT_SHLIB 5
#define PT_PHDR 6
/* Values for p_flags */
#define PF_X (1 << 0) /* Segment is executable */
#define PF_W (1 << 1) /* Segment is writable */
#define PF_R (1 << 2) /* Segment is readable */
/* ELF 32-bit program header */
struct elf32_phdr
{
uint32_t p_type;
elf_off_t p_offset;
elf_addr_t p_vaddr;
elf_addr_t p_paddr;
uint32_t p_filesz;
uint32_t p_memsz;
uint32_t p_flags;
uint32_t p_align;
};
#endif /* __ELF_H__ */

View file

@ -0,0 +1,94 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2026 Aidan MacDonald
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef __ELF_LOADER_H__
#define __ELF_LOADER_H__
/*
* Primitive ELF loader -- allows loading an ELF binary from
* a file descriptor into RAM. Only static binaries linked at
* a fixed address are supported.
*
* The caller supplies a memory map which limits where the
* ELF file can be loaded; if a program header requests any
* memory outside of this mapping, loading will fail. There
* is no support for relocation, PIC code, or any form of
* dynamic linking.
*
* The loader can return the entry point of the ELF binary.
* It does not support arbitrary symbol lookups.
*/
#include <stdint.h>
#include <stddef.h>
enum elf_error
{
ELF_OK,
ELF_ERR_FILE_NOT_FOUND,
ELF_ERR_IO,
ELF_ERR_EMPTY,
ELF_ERR_BADMAGIC,
ELF_ERR_UNSUPP_ARCH,
ELF_ERR_UNSUPP_OS,
ELF_ERR_UNSUPP_VERSION,
ELF_ERR_UNSUPP_TYPE,
ELF_ERR_UNSUPP_FORMAT,
ELF_ERR_UNSUPP_PHSIZE,
ELF_ERR_TOO_MANY_PHDRS,
ELF_ERR_UNSUPP_PHDR,
ELF_ERR_INVALID_PHDR,
ELF_ERR_MEM_INCOMPAT_FLAGS,
ELF_ERR_MEM_UNMAPPED,
};
/*
* Describes an entry in the virtual memory map specified
* when loading an ELF file.
*
* ELF segments must fit completely within a single entry
* in the memory map. Segments crossing multiple mappings
* are rejected even if the mappings are contiguous.
*
* A segment may also be rejected if it has flags (R/W/X)
* not present in the corresponding memory map entry.
*/
struct elf_memory_map
{
uintptr_t addr;
size_t size;
uint32_t flags;
};
struct elf_load_context
{
const struct elf_memory_map *mmap;
size_t num_mmap;
};
int elf_loadfd(int fd,
const struct elf_load_context *ctx,
void **entrypoint);
int elf_loadpath(const char *filename,
const struct elf_load_context *ctx,
void **entrypoint);
#endif /* __ELF_LOADER_H__ */