firmware: add simple ELF loader for static binaries
rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
| Newsgroups | gmane.comp.systems.archos.rockbox.cvs |
|---|---|
| Message-ID | <[email protected]> |
commit a610998ea4544bf25d2562268a866163c70b0d59 Author: Aidan MacDonald <[email protected]> Date: Sun Jan 4 00:00:05 2026 +0000 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 diff --git a/firmware/SOURCES b/firmware/SOURCES index 9279307af5..88927f7364 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -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 diff --git a/firmware/elf_loader.c b/firmware/elf_loader.c new file mode 100644 index 0000000000..d6fd2e71d4 --- /dev/null +++ b/firmware/elf_loader.c @@ -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; +} diff --git a/firmware/include/elf-target.h b/firmware/include/elf-target.h new file mode 100644 index 0000000000..e718cf666b --- /dev/null +++ b/firmware/include/elf-target.h @@ -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__ */ diff --git a/firmware/include/elf.h b/firmware/include/elf.h new file mode 100644 index 0000000000..bfdd4f2281 --- /dev/null +++ b/firmware/include/elf.h @@ -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__ */ diff --git a/firmware/include/elf_loader.h b/firmware/include/elf_loader.h new file mode 100644 index 0000000000..0f30e87ab2 --- /dev/null +++ b/firmware/include/elf_loader.h @@ -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__ */ -- rockbox-cvs mailing list [email protected] https://lists.haxx.se/mailman/listinfo/rockbox-cvs