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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.