/* ChibiOS - Copyright (C) 2006,2007,2008,2009,2010,2011,2012,2013,2014, 2015,2016,2017,2018,2019,2020,2021 Giovanni Di Sirio. This file is part of ChibiOS. ChibiOS 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 version 3 of the License. ChibiOS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** * @file sb/host/sbelf.c * @brief ARM SandBox ELF loader code. * * @addtogroup ARM_SANDBOX_ELF * @{ */ #include #include "ch.h" #include "sb.h" #if (SB_CFG_ENABLE_VFS == TRUE) || defined(__DOXYGEN__) /*===========================================================================*/ /* Module local definitions. */ /*===========================================================================*/ /* Relevant ELF file types.*/ #define ET_EXEC 2U /* Relevant section types.*/ #define SHT_PROGBITS 1U #define SHT_SYMTAB 2U #define SHT_NOBITS 8U #define SHT_REL 9U /* Relevant section flags.*/ #define SHF_WRITE (1U << 0) #define SHF_ALLOC (1U << 1) #define SHF_EXECINSTR (1U << 2) #define SHF_INFO_LINK (1U << 6) /* Special section indices.*/ #define SHN_UNDEF 0U /* Supported relocation types.*/ #define R_ARM_ABS32 2U #define R_ARM_THM_PC22 10U #define R_ARM_THM_JUMP24 30U #define R_ARM_PREL31 42U #define R_ARM_THM_MOVW_ABS_NC 47U #define R_ARM_THM_MOVT_ABS 48U #define ELF32_R_SYM(v) ((v) >> 8) #define ELF32_R_TYPE(v) ((v) & 0xFFU) /*===========================================================================*/ /* Module exported variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Module local types. */ /*===========================================================================*/ /** * @brief Type of a section number. */ typedef unsigned elf_secnum_t; /** * @brief Type of a symbol number. */ //typedef unsigned elf_symnum_t; /** * @brief Type of a loadable section info structure. */ typedef struct { elf_secnum_t section; memory_area_t area; size_t rel_size; vfs_offset_t rel_off; } elf_section_info_t; /** * @brief Type of an ELF loader context. */ typedef struct elf_load_context { vfs_file_node_c *fnp; const memory_area_t *map; // uint32_t entry; elf_secnum_t sections_num; vfs_offset_t sections_off; bool rel_movw_found; uint32_t rel_movw_symbol; uint32_t rel_movw_address; elf_section_info_t *next; elf_section_info_t allocated[SB_CFG_ELF_MAX_ALLOCATED]; } elf_load_context_t; /** * @brief Type of an ELF32 header. */ typedef struct { unsigned char e_ident[16]; uint16_t e_type; uint16_t e_machine; uint32_t e_version; uint32_t e_entry; uint32_t e_phoff; uint32_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; } elf32_header_t; typedef struct { uint32_t sh_name; uint32_t sh_type; uint32_t sh_flags; uint32_t sh_addr; uint32_t sh_offset; uint32_t sh_size; uint32_t sh_link; uint32_t sh_info; uint32_t sh_addralign; uint32_t sh_entsize; } elf32_section_header_t; typedef struct { uint32_t r_offset; uint32_t r_info; } elf32_rel_t; typedef struct { uint32_t st_name; uint32_t st_value; uint32_t st_size; uint8_t st_info; uint8_t st_other; uint16_t st_shndx; } elf32_symbol_t; /*===========================================================================*/ /* Module local variables. */ /*===========================================================================*/ /*===========================================================================*/ /* Module local functions. */ /*===========================================================================*/ static msg_t area_is_intersecting(elf_load_context_t *ctxp, const memory_area_t *map) { elf_section_info_t *esip; /* Scanning allocated sections.*/ for (esip = &ctxp->allocated[0]; esip < ctxp->next; esip++) { if (chMemIsAreaIntersectingX(&esip->area, map)) { return true; } } return false; } static msg_t allocate_section(elf_load_context_t *ctxp, elf_secnum_t section, const elf32_section_header_t *shp) { elf_section_info_t *esip; /* Checking if there is space in the sections table.*/ if (ctxp->next >= &ctxp->allocated[SB_CFG_ELF_MAX_ALLOCATED]) { return CH_RET_ENOMEM; } /* Adding an entry for this section.*/ esip = ctxp->next; esip->section = section; esip->area.base = ctxp->map->base + (size_t)shp->sh_addr; esip->area.size = (size_t)shp->sh_size; /* Checking if the section can fit into the destination memory area.*/ if (!chMemIsAreaWithinX(ctxp->map, &esip->area)) { return CH_RET_ENOMEM; } /* Checking if this section is overlapping some other allocated section.*/ if (area_is_intersecting(ctxp, &esip->area)) { return CH_RET_ENOEXEC; } ctxp->next++; return CH_RET_SUCCESS; } static msg_t allocate_load_section(elf_load_context_t *ctxp, elf_secnum_t section, const elf32_section_header_t *shp) { elf_section_info_t *esip; msg_t ret; /* Checking if there is space in the sections table.*/ if (ctxp->next >= &ctxp->allocated[SB_CFG_ELF_MAX_ALLOCATED]) { return CH_RET_ENOMEM; } /* Adding an entry for this section.*/ esip = ctxp->next; esip->section = section; esip->area.base = ctxp->map->base + (size_t)shp->sh_addr; esip->area.size = (size_t)shp->sh_size; /* Checking if the section can fit into the destination memory area.*/ if (!chMemIsAreaWithinX(ctxp->map, &esip->area)) { return CH_RET_ENOMEM; } /* Checking if this section is overlapping some other allocated section.*/ if (area_is_intersecting(ctxp, &esip->area)) { return CH_RET_ENOEXEC; } /* Loading section data.*/ ret = vfsSetFilePosition(ctxp->fnp, (vfs_offset_t)shp->sh_offset, VFS_SEEK_SET); CH_RETURN_ON_ERROR(ret); ret = vfsReadFile(ctxp->fnp, (void *)esip->area.base, esip->area.size); CH_RETURN_ON_ERROR(ret); ctxp->next++; return ret; } static elf_section_info_t *find_allocated_section(elf_load_context_t *ctxp, elf_secnum_t section) { elf_section_info_t *esip; /* Scanning allocated sections.*/ for (esip = &ctxp->allocated[0]; esip < ctxp->next; esip++) { if (esip->section == section) { return esip; } } return NULL; } static uint32_t get_const16(uint32_t address) { uint32_t ins = ((uint32_t)(((uint16_t *)address)[0]) << 16) | ((uint32_t)(((uint16_t *)address)[1]) << 0); return ((ins & 0x000000FFU) >> 0) | ((ins & 0x00007000U) >> 4) | ((ins & 0x000F0000U) >> 4) | ((ins & 0x04000000U) >> 14); } static void set_const16(uint32_t address, uint32_t val16) { uint32_t ins = ((uint32_t)(((uint16_t *)address)[0]) << 16) | ((uint32_t)(((uint16_t *)address)[1]) << 0); ins &= ~0x040F70FFU; ins |= ((val16 & 0x00000800U) << 14) | ((val16 & 0x0000F000U) << 4) | ((val16 & 0x00000700U) << 4) | ((val16 & 0x000000FFU) << 0); ((uint16_t *)address)[0] = (uint16_t)(ins >> 16); ((uint16_t *)address)[1] = (uint16_t)(ins >> 0); } static msg_t reloc_entry(elf_load_context_t *ctxp, elf_section_info_t *esip, elf32_rel_t *rp) { uint32_t relocation_address, offset; /* Relocation point address.*/ relocation_address = (uint32_t)ctxp->map->base + rp->r_offset; if (!chMemIsSpaceWithinX(&esip->area, (const void *)relocation_address, sizeof (uint32_t))) { return CH_RET_ENOMEM; } /* Handling the various relocation point types.*/ switch (ELF32_R_TYPE(rp->r_info)) { case R_ARM_ABS32: *((uint32_t *)relocation_address) += (uint32_t)ctxp->map->base; break; case R_ARM_THM_MOVW_ABS_NC: /* Checking for consecutive "movw" relocations without a "movt", we consider this an error.*/ if (ctxp->rel_movw_found) { return CH_RET_ENOEXEC; } /* Storing information about the "movw" instruction to be processed later when the associated "movt" instruction is found.*/ ctxp->rel_movw_found = true; ctxp->rel_movw_symbol = ELF32_R_SYM(rp->r_info); ctxp->rel_movw_address = relocation_address; break; case R_ARM_THM_MOVT_ABS: /* Checking if we found a "movw" instruction before this "movt".*/ if (!ctxp->rel_movw_found) { return CH_RET_ENOEXEC; } /* Checking if both instructions referred to the same symbol.*/ if (ctxp->rel_movw_symbol != ELF32_R_SYM(rp->r_info)) { return CH_RET_ENOEXEC; } /* Relocating both the "movw" and the "movt" instructions.*/ offset = (get_const16(relocation_address) << 16) | (get_const16(ctxp->rel_movw_address) << 0); offset += (uint32_t)ctxp->map->base; set_const16(relocation_address, offset >> 16); set_const16(ctxp->rel_movw_address, offset & 0xFFFFU); ctxp->rel_movw_found = false; break; case R_ARM_THM_PC22: case R_ARM_THM_JUMP24: case R_ARM_PREL31: /* To be ignored.*/ break; default: /* Anything unexpected is handled as an error.*/ return CH_RET_ENOEXEC; } return CH_RET_SUCCESS; } static msg_t reloc_section(elf_load_context_t *ctxp, elf_section_info_t *esip) { vfs_shared_buffer_t *shbuf; elf32_rel_t *rbuf; size_t size, done_size, remaining_size; msg_t ret; shbuf = vfs_buffer_take_wait(); rbuf = (elf32_rel_t *)(void *)shbuf->buf; /* Reading the relocation section data.*/ remaining_size = esip->rel_size; done_size = 0U; while (remaining_size > 0U) { unsigned i, n; /* Reading relocation data using buffers in order to not make continuous calls to the FS which could be unbuffered.*/ if (remaining_size > VFS_BUFFER_SIZE) { size = VFS_BUFFER_SIZE; } else { size = remaining_size; } /* Reading a buffer-worth of relocation data.*/ ret = vfsSetFilePosition(ctxp->fnp, esip->rel_off + (vfs_offset_t)done_size, VFS_SEEK_SET); CH_BREAK_ON_ERROR(ret); ret = vfsReadFile(ctxp->fnp, (void *)rbuf, size); CH_BREAK_ON_ERROR(ret); /* Number of relocation entries in the buffer.*/ n = (unsigned)ret / (unsigned)sizeof (elf32_rel_t); for (i = 0U; i < n; i++) { ret = reloc_entry(ctxp, esip, &rbuf[i]); CH_BREAK_ON_ERROR(ret); } CH_BREAK_ON_ERROR(ret); remaining_size -= size; done_size += size; } vfs_buffer_release(shbuf); return ret; } /*===========================================================================*/ /* Module exported functions. */ /*===========================================================================*/ msg_t sbElfLoad(vfs_file_node_c *fnp, const memory_area_t *map) { msg_t ret; elf_load_context_t ctx; elf_section_info_t *esip; /* Large structures not used at same time, the compiler could optimize it but it is still a problem when running the code without optimizations for debug.*/ union { elf32_header_t h; elf32_section_header_t sh; } u; /* Load context initialization.*/ { static const uint8_t elf32_header[16] = { 0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* Context fully cleared.*/ memset((void *)&ctx, 0, sizeof (elf_load_context_t)); /* Initializing the fixed part of the context.*/ ctx.fnp = fnp; ctx.map = map; ctx.next = &ctx.allocated[0]; /* Reading the main ELF header.*/ ret = vfsSetFilePosition(ctx.fnp, (vfs_offset_t)0, VFS_SEEK_SET); CH_RETURN_ON_ERROR(ret); ret = vfsReadFile(ctx.fnp, (void *)&u.h, sizeof (elf32_header_t)); CH_RETURN_ON_ERROR(ret); /* Checking for the expected header.*/ if (memcmp(u.h.e_ident, elf32_header, 16) != 0) { return CH_RET_ENOEXEC; } /* Accepting executable files only.*/ if (u.h.e_type != ET_EXEC) { return CH_RET_ENOEXEC; } /* TODO more consistency checks.*/ /* Storing info required later.*/ // ctx.entry = u.h.e_entry; ctx.sections_num = (unsigned)u.h.e_shnum; ctx.sections_off = (vfs_offset_t)u.h.e_shoff; } /* Loading phase, scanning section headers.*/ { elf_secnum_t i; for (i = 0U; i < ctx.sections_num; i++) { /* Reading the header.*/ ret = vfsSetFilePosition(ctx.fnp, ctx.sections_off + ((vfs_offset_t)i * (vfs_offset_t)sizeof (elf32_section_header_t)), VFS_SEEK_SET); CH_RETURN_ON_ERROR(ret); ret = vfsReadFile(ctx.fnp, (void *)&u.sh, sizeof (elf32_section_header_t)); CH_RETURN_ON_ERROR(ret); /* Empty sections are not processed.*/ if (u.sh.sh_size == 0U) { continue; } /* Deciding what to do with the section depending on type.*/ switch (u.sh.sh_type) { case SHT_PROGBITS: /* Allocatable section type, needs to be loaded.*/ if ((u.sh.sh_flags & SHF_ALLOC) != 0U) { /* Allocating and loading, could fail.*/ ret = allocate_load_section(&ctx, i, &u.sh); CH_RETURN_ON_ERROR(ret); } break; case SHT_NOBITS: /* Uninitialized data section, we can have more than one, just checking address ranges.*/ if ((u.sh.sh_flags & SHF_ALLOC) != 0U) { ret = allocate_section(&ctx, i, &u.sh); CH_RETURN_ON_ERROR(ret); } break; case SHT_REL: if ((u.sh.sh_flags & SHF_INFO_LINK) != 0U) { esip = find_allocated_section(&ctx, (elf_secnum_t)u.sh.sh_info); if (esip == NULL) { /* Ignoring other relocation sections.*/ break; } /* Multiple relocation sections associated to the same section.*/ if (esip->rel_size != 0U) { return CH_RET_ENOEXEC; } esip->rel_size = u.sh.sh_size; esip->rel_off = (vfs_offset_t)u.sh.sh_offset; } break; default: /* Ignoring other section types.*/ break; } } } /* Relocating all sections with an associated relocation table.*/ for (esip = &ctx.allocated[0]; esip < ctx.next; esip++) { if (esip->rel_off != (vfs_offset_t)0) { ret = reloc_section(&ctx, esip); CH_RETURN_ON_ERROR(ret); } } return ret; } msg_t sbElfLoadFile(vfs_driver_c *drvp, const char *path, const memory_area_t *map) { vfs_file_node_c *fnp; msg_t ret; ret = vfsDrvOpenFile(drvp, path, VO_RDONLY, &fnp); CH_RETURN_ON_ERROR(ret); do { ret = sbElfLoad(fnp, map); CH_BREAK_ON_ERROR(ret); } while (false); vfsClose((vfs_node_c *)fnp); return ret; } #endif /* SB_CFG_ENABLE_VFS == TRUE */ /** @} */