ELF-Loader: C++ plugin support, loader overhaul. (#1744)

* fap-loader: load all code and data sections
* fap-loader: relocate all code and data sections
* fap-loader: remove old elf loader
* fap-loader: new jmp call relocation
* openocd: resume on detach
* fap-loader: trampoline for big jumps
* fap-loader: rename cache
* fap-loader: init_array support
* fap-loader: untangled flipper_application into separate entities
* fap-loader: fix debug
* fap-loader: optimize section container
* fap-loader: optimize key for section container
* fap-loader: disable debug log
* documentation
* F7: bump api symbols version
* Lib: cleanup elf_file.c

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Sergey Gavrilov 2022-09-26 00:11:29 +10:00 committed by GitHub
parent 7e2008095e
commit e6d22ed147
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1094 additions and 649 deletions

View file

@ -25,7 +25,7 @@ static bool
FlipperApplication* app = flipper_application_alloc(loader->storage, &hashtable_api_interface);
FlipperApplicationPreloadStatus preload_res =
flipper_application_preload(app, string_get_cstr(path));
flipper_application_preload_manifest(app, string_get_cstr(path));
bool load_success = false;

View file

@ -64,7 +64,7 @@ class AppState:
def is_loaded_in_gdb(self, gdb_app) -> bool:
# Avoid constructing full app wrapper for comparison
return self.entry_address == int(gdb_app["entry"])
return self.entry_address == int(gdb_app["state"]["entry"])
@staticmethod
def parse_debug_link_data(section_data: bytes) -> Tuple[str, int]:
@ -78,13 +78,13 @@ class AppState:
@staticmethod
def from_gdb(gdb_app: "AppState") -> "AppState":
state = AppState(str(gdb_app["manifest"]["name"].string()))
state.entry_address = int(gdb_app["entry"])
state.entry_address = int(gdb_app["state"]["entry"])
app_state = gdb_app["state"]
if debug_link_size := int(app_state["debug_link_size"]):
if debug_link_size := int(app_state["debug_link_info"]["debug_link_size"]):
debug_link_data = (
gdb.selected_inferior()
.read_memory(int(app_state["debug_link"]), debug_link_size)
.read_memory(int(app_state["debug_link_info"]["debug_link"]), debug_link_size)
.tobytes()
)
state.debug_link_elf, state.debug_link_crc = AppState.parse_debug_link_data(

View file

@ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config {
# assignment
mmw 0xE0042004 0x00000020 0
}
$_TARGETNAME configure -event gdb-detach {
resume
}

View file

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,1.9,,
Version,+,1.10,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
Header,+,applications/services/cli/cli_vcp.h,,
@ -779,13 +779,13 @@ Function,-,fiprintf,int,"FILE*, const char*, ..."
Function,-,fiscanf,int,"FILE*, const char*, ..."
Function,+,flipper_application_alloc,FlipperApplication*,"Storage*, const ElfApiInterface*"
Function,+,flipper_application_free,void,FlipperApplication*
Function,-,flipper_application_get_entry_address,const void*,FlipperApplication*
Function,+,flipper_application_get_manifest,const FlipperApplicationManifest*,FlipperApplication*
Function,-,flipper_application_get_state,const FlipperApplicationState*,FlipperApplication*
Function,-,flipper_application_get_thread,FuriThread*,FlipperApplication*
Function,+,flipper_application_load_status_to_string,const char*,FlipperApplicationLoadStatus
Function,+,flipper_application_manifest_is_compatible,_Bool,"const FlipperApplicationManifest*, const ElfApiInterface*"
Function,+,flipper_application_manifest_is_valid,_Bool,const FlipperApplicationManifest*
Function,+,flipper_application_map_to_memory,FlipperApplicationLoadStatus,FlipperApplication*
Function,+,flipper_application_preload,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*"
Function,+,flipper_application_preload_manifest,FlipperApplicationPreloadStatus,"FlipperApplication*, const char*"
Function,-,flipper_application_preload_status_to_string,const char*,FlipperApplicationPreloadStatus
Function,+,flipper_application_spawn,FuriThread*,"FlipperApplication*, void*"
Function,+,flipper_format_buffered_file_alloc,FlipperFormat*,Storage*

1 entry status name type params
2 Version + 1.9 1.10
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/cli/cli.h
5 Header + applications/services/cli/cli_vcp.h
779 Function - fiscanf int FILE*, const char*, ...
780 Function + flipper_application_alloc FlipperApplication* Storage*, const ElfApiInterface*
781 Function + flipper_application_free void FlipperApplication*
Function - flipper_application_get_entry_address const void* FlipperApplication*
782 Function + flipper_application_get_manifest const FlipperApplicationManifest* FlipperApplication*
Function - flipper_application_get_state const FlipperApplicationState* FlipperApplication*
Function - flipper_application_get_thread FuriThread* FlipperApplication*
783 Function + flipper_application_load_status_to_string const char* FlipperApplicationLoadStatus
784 Function + flipper_application_manifest_is_compatible _Bool const FlipperApplicationManifest*, const ElfApiInterface*
785 Function + flipper_application_manifest_is_valid _Bool const FlipperApplicationManifest*
786 Function + flipper_application_map_to_memory FlipperApplicationLoadStatus FlipperApplication*
787 Function + flipper_application_preload FlipperApplicationPreloadStatus FlipperApplication*, const char*
788 Function + flipper_application_preload_manifest FlipperApplicationPreloadStatus FlipperApplication*, const char*
789 Function - flipper_application_preload_status_to_string const char* FlipperApplicationPreloadStatus
790 Function + flipper_application_spawn FuriThread* FlipperApplication*, void*
791 Function + flipper_format_buffered_file_alloc FlipperFormat* Storage*

View file

@ -0,0 +1,21 @@
#include "application_manifest.h"
bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest) {
if((manifest->base.manifest_magic != FAP_MANIFEST_MAGIC) ||
(manifest->base.manifest_version != FAP_MANIFEST_SUPPORTED_VERSION)) {
return false;
}
return true;
}
bool flipper_application_manifest_is_compatible(
const FlipperApplicationManifest* manifest,
const ElfApiInterface* api_interface) {
if(manifest->base.api_version.major != api_interface->api_version_major /* ||
manifest->base.api_version.minor > app->api_interface->api_version_minor */) {
return false;
}
return true;
}

View file

@ -1,6 +1,12 @@
/**
* @file application_manifest.h
* Flipper application manifest
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "elf/elf_api_interface.h"
#ifdef __cplusplus
extern "C" {
@ -40,6 +46,25 @@ typedef FlipperApplicationManifestV1 FlipperApplicationManifest;
#pragma pack(pop)
/**
* @brief Check if manifest is valid
*
* @param manifest
* @return bool
*/
bool flipper_application_manifest_is_valid(const FlipperApplicationManifest* manifest);
/**
* @brief Check if manifest is compatible with current ELF API interface
*
* @param manifest
* @param api_interface
* @return bool
*/
bool flipper_application_manifest_is_compatible(
const FlipperApplicationManifest* manifest,
const ElfApiInterface* api_interface);
#ifdef __cplusplus
}
#endif

View file

@ -1,6 +1,6 @@
#pragma once
#include <flipper_application/elf/elf.h>
#include <elf.h>
#include <stdbool.h>
#define ELF_INVALID_ADDRESS 0xFFFFFFFF

View file

@ -0,0 +1,794 @@
#include <elf.h>
#include "elf_file.h"
#include "elf_file_i.h"
#include "elf_api_interface.h"
#define TAG "elf"
#define ELF_NAME_BUFFER_LEN 32
#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr))
#define IS_FLAGS_SET(v, m) ((v & m) == m)
#define RESOLVER_THREAD_YIELD_STEP 30
// #define ELF_DEBUG_LOG 1
#ifndef ELF_DEBUG_LOG
#undef FURI_LOG_D
#define FURI_LOG_D(...)
#endif
#define TRAMPOLINE_CODE_SIZE 6
/**
ldr r12, [pc, #2]
bx r12
*/
const uint8_t trampoline_code_little_endian[TRAMPOLINE_CODE_SIZE] =
{0xdf, 0xf8, 0x02, 0xc0, 0x60, 0x47};
typedef struct {
uint8_t code[TRAMPOLINE_CODE_SIZE];
uint32_t addr;
} __attribute__((packed)) JMPTrampoline;
/**************************************************************************************************/
/********************************************* Caches *********************************************/
/**************************************************************************************************/
static bool address_cache_get(AddressCache_t cache, int symEntry, Elf32_Addr* symAddr) {
Elf32_Addr* addr = AddressCache_get(cache, symEntry);
if(addr) {
*symAddr = *addr;
return true;
} else {
return false;
}
}
static void address_cache_put(AddressCache_t cache, int symEntry, Elf32_Addr symAddr) {
AddressCache_set_at(cache, symEntry, symAddr);
}
/**************************************************************************************************/
/********************************************** ELF ***********************************************/
/**************************************************************************************************/
static ELFSection* elf_file_get_section(ELFFile* elf, const char* name) {
return ELFSectionDict_get(elf->sections, name);
}
static void elf_file_put_section(ELFFile* elf, const char* name, ELFSection* section) {
ELFSectionDict_set_at(elf->sections, strdup(name), *section);
}
static bool elf_read_string_from_offset(ELFFile* elf, off_t offset, string_t name) {
bool result = false;
off_t old = storage_file_tell(elf->fd);
do {
if(!storage_file_seek(elf->fd, offset, true)) break;
char buffer[ELF_NAME_BUFFER_LEN + 1];
buffer[ELF_NAME_BUFFER_LEN] = 0;
while(true) {
uint16_t read = storage_file_read(elf->fd, buffer, ELF_NAME_BUFFER_LEN);
string_cat_str(name, buffer);
if(strlen(buffer) < ELF_NAME_BUFFER_LEN) {
result = true;
break;
}
if(storage_file_get_error(elf->fd) != FSE_OK || read == 0) break;
}
} while(false);
storage_file_seek(elf->fd, old, true);
return result;
}
static bool elf_read_section_name(ELFFile* elf, off_t offset, string_t name) {
return elf_read_string_from_offset(elf, elf->section_table_strings + offset, name);
}
static bool elf_read_symbol_name(ELFFile* elf, off_t offset, string_t name) {
return elf_read_string_from_offset(elf, elf->symbol_table_strings + offset, name);
}
static bool elf_read_section_header(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header) {
off_t offset = SECTION_OFFSET(elf, section_idx);
return storage_file_seek(elf->fd, offset, true) &&
storage_file_read(elf->fd, section_header, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr);
}
static bool
elf_read_section(ELFFile* elf, size_t section_idx, Elf32_Shdr* section_header, string_t name) {
if(!elf_read_section_header(elf, section_idx, section_header)) {
return false;
}
if(section_header->sh_name && !elf_read_section_name(elf, section_header->sh_name, name)) {
return false;
}
return true;
}
static bool elf_read_symbol(ELFFile* elf, int n, Elf32_Sym* sym, string_t name) {
bool success = false;
off_t old = storage_file_tell(elf->fd);
off_t pos = elf->symbol_table + n * sizeof(Elf32_Sym);
if(storage_file_seek(elf->fd, pos, true) &&
storage_file_read(elf->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) {
if(sym->st_name)
success = elf_read_symbol_name(elf, sym->st_name, name);
else {
Elf32_Shdr shdr;
success = elf_read_section(elf, sym->st_shndx, &shdr, name);
}
}
storage_file_seek(elf->fd, old, true);
return success;
}
static ELFSection* elf_section_of(ELFFile* elf, int index) {
ELFSectionDict_it_t it;
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
if(itref->value.sec_idx == index) {
return &itref->value;
}
}
return NULL;
}
static Elf32_Addr elf_address_of(ELFFile* elf, Elf32_Sym* sym, const char* sName) {
if(sym->st_shndx == SHN_UNDEF) {
Elf32_Addr addr = 0;
if(elf->api_interface->resolver_callback(sName, &addr)) {
return addr;
}
} else {
ELFSection* symSec = elf_section_of(elf, sym->st_shndx);
if(symSec) {
return ((Elf32_Addr)symSec->data) + sym->st_value;
}
}
FURI_LOG_D(TAG, " Can not find address for symbol %s", sName);
return ELF_INVALID_ADDRESS;
}
__attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) {
#define STRCASE(name) \
case name: \
return #name;
switch(symt) {
STRCASE(R_ARM_NONE)
STRCASE(R_ARM_TARGET1)
STRCASE(R_ARM_ABS32)
STRCASE(R_ARM_THM_PC22)
STRCASE(R_ARM_THM_JUMP24)
default:
return "R_<unknow>";
}
#undef STRCASE
}
static JMPTrampoline* elf_create_trampoline(Elf32_Addr addr) {
JMPTrampoline* trampoline = malloc(sizeof(JMPTrampoline));
memcpy(trampoline->code, trampoline_code_little_endian, TRAMPOLINE_CODE_SIZE);
trampoline->addr = addr;
return trampoline;
}
static void elf_relocate_jmp_call(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
int offset, hi, lo, s, j1, j2, i1, i2, imm10, imm11;
int to_thumb, is_call, blx_bit = 1 << 12;
/* Get initial offset */
hi = ((uint16_t*)relAddr)[0];
lo = ((uint16_t*)relAddr)[1];
s = (hi >> 10) & 1;
j1 = (lo >> 13) & 1;
j2 = (lo >> 11) & 1;
i1 = (j1 ^ s) ^ 1;
i2 = (j2 ^ s) ^ 1;
imm10 = hi & 0x3ff;
imm11 = lo & 0x7ff;
offset = (s << 24) | (i1 << 23) | (i2 << 22) | (imm10 << 12) | (imm11 << 1);
if(offset & 0x01000000) offset -= 0x02000000;
to_thumb = symAddr & 1;
is_call = (type == R_ARM_THM_PC22);
/* Store offset */
int offset_copy = offset;
/* Compute final offset */
offset += symAddr - relAddr;
if(!to_thumb && is_call) {
blx_bit = 0; /* bl -> blx */
offset = (offset + 3) & -4; /* Compute offset from aligned PC */
}
/* Check that relocation is possible
* offset must not be out of range
* if target is to be entered in arm mode:
- bit 1 must not set
- instruction must be a call (bl) or a jump to PLT */
if(!to_thumb || offset >= 0x1000000 || offset < -0x1000000) {
if(to_thumb || (symAddr & 2) || (!is_call)) {
FURI_LOG_D(
TAG,
"can't relocate value at %x, %s, doing trampoline",
relAddr,
elf_reloc_type_to_str(type));
Elf32_Addr addr;
if(!address_cache_get(elf->trampoline_cache, symAddr, &addr)) {
addr = (Elf32_Addr)elf_create_trampoline(symAddr);
address_cache_put(elf->trampoline_cache, symAddr, addr);
}
offset = offset_copy;
offset += (int)addr - relAddr;
if(!to_thumb && is_call) {
blx_bit = 0; /* bl -> blx */
offset = (offset + 3) & -4; /* Compute offset from aligned PC */
}
}
}
/* Compute and store final offset */
s = (offset >> 24) & 1;
i1 = (offset >> 23) & 1;
i2 = (offset >> 22) & 1;
j1 = s ^ (i1 ^ 1);
j2 = s ^ (i2 ^ 1);
imm10 = (offset >> 12) & 0x3ff;
imm11 = (offset >> 1) & 0x7ff;
(*(uint16_t*)relAddr) = (uint16_t)((hi & 0xf800) | (s << 10) | imm10);
(*(uint16_t*)(relAddr + 2)) =
(uint16_t)((lo & 0xc000) | (j1 << 13) | blx_bit | (j2 << 11) | imm11);
}
static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
switch(type) {
case R_ARM_TARGET1:
case R_ARM_ABS32:
*((uint32_t*)relAddr) += symAddr;
FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
break;
case R_ARM_THM_PC22:
case R_ARM_THM_JUMP24:
elf_relocate_jmp_call(elf, relAddr, type, symAddr);
FURI_LOG_D(
TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
break;
default:
FURI_LOG_E(TAG, " Undefined relocation %d", type);
return false;
}
return true;
}
static bool elf_relocate(ELFFile* elf, Elf32_Shdr* h, ELFSection* s) {
if(s->data) {
Elf32_Rel rel;
size_t relEntries = h->sh_size / sizeof(rel);
size_t relCount;
(void)storage_file_seek(elf->fd, h->sh_offset, true);
FURI_LOG_D(TAG, " Offset Info Type Name");
int relocate_result = true;
string_t symbol_name;
string_init(symbol_name);
for(relCount = 0; relCount < relEntries; relCount++) {
if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) {
FURI_LOG_D(TAG, " reloc YIELD");
furi_delay_tick(1);
}
if(storage_file_read(elf->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) {
FURI_LOG_E(TAG, " reloc read fail");
string_clear(symbol_name);
return false;
}
Elf32_Addr symAddr;
int symEntry = ELF32_R_SYM(rel.r_info);
int relType = ELF32_R_TYPE(rel.r_info);
Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset;
if(!address_cache_get(elf->relocation_cache, symEntry, &symAddr)) {
Elf32_Sym sym;
string_reset(symbol_name);
if(!elf_read_symbol(elf, symEntry, &sym, symbol_name)) {
FURI_LOG_E(TAG, " symbol read fail");
string_clear(symbol_name);
return false;
}
FURI_LOG_D(
TAG,
" %08X %08X %-16s %s",
(unsigned int)rel.r_offset,
(unsigned int)rel.r_info,
elf_reloc_type_to_str(relType),
string_get_cstr(symbol_name));
symAddr = elf_address_of(elf, &sym, string_get_cstr(symbol_name));
address_cache_put(elf->relocation_cache, symEntry, symAddr);
}
if(symAddr != ELF_INVALID_ADDRESS) {
FURI_LOG_D(
TAG,
" symAddr=%08X relAddr=%08X",
(unsigned int)symAddr,
(unsigned int)relAddr);
if(!elf_relocate_symbol(elf, relAddr, relType, symAddr)) {
relocate_result = false;
}
} else {
FURI_LOG_E(TAG, " No symbol address of %s", string_get_cstr(symbol_name));
relocate_result = false;
}
}
string_clear(symbol_name);
return relocate_result;
} else {
FURI_LOG_D(TAG, "Section not loaded");
}
return false;
}
/**************************************************************************************************/
/********************************************* MISC ***********************************************/
/**************************************************************************************************/
static bool cstr_prefix(const char* prefix, const char* string) {
return strncmp(prefix, string, strlen(prefix)) == 0;
}
/**************************************************************************************************/
/************************************ Internal FAP interfaces *************************************/
/**************************************************************************************************/
typedef enum {
SectionTypeERROR = 0,
SectionTypeUnused = 1 << 0,
SectionTypeData = 1 << 1,
SectionTypeRelData = 1 << 2,
SectionTypeSymTab = 1 << 3,
SectionTypeStrTab = 1 << 4,
SectionTypeManifest = 1 << 5,
SectionTypeDebugLink = 1 << 6,
SectionTypeValid = SectionTypeSymTab | SectionTypeStrTab | SectionTypeManifest,
} SectionType;
static bool elf_load_metadata(
ELFFile* elf,
Elf32_Shdr* section_header,
FlipperApplicationManifest* manifest) {
if(section_header->sh_size < sizeof(FlipperApplicationManifest)) {
return false;
}
if(manifest == NULL) {
return true;
}
return storage_file_seek(elf->fd, section_header->sh_offset, true) &&
storage_file_read(elf->fd, manifest, section_header->sh_size) ==
section_header->sh_size;
}
static bool elf_load_debug_link(ELFFile* elf, Elf32_Shdr* section_header) {
elf->debug_link_info.debug_link_size = section_header->sh_size;
elf->debug_link_info.debug_link = malloc(section_header->sh_size);
return storage_file_seek(elf->fd, section_header->sh_offset, true) &&
storage_file_read(elf->fd, elf->debug_link_info.debug_link, section_header->sh_size) ==
section_header->sh_size;
}
static SectionType elf_preload_section(
ELFFile* elf,
size_t section_idx,
Elf32_Shdr* section_header,
string_t name_string,
FlipperApplicationManifest* manifest) {
const char* name = string_get_cstr(name_string);
const struct {
const char* prefix;
SectionType type;
} lookup_sections[] = {
{".text", SectionTypeData},
{".rodata", SectionTypeData},
{".data", SectionTypeData},
{".bss", SectionTypeData},
{".preinit_array", SectionTypeData},
{".init_array", SectionTypeData},
{".fini_array", SectionTypeData},
{".rel.text", SectionTypeRelData},
{".rel.rodata", SectionTypeRelData},
{".rel.data", SectionTypeRelData},
{".rel.preinit_array", SectionTypeRelData},
{".rel.init_array", SectionTypeRelData},
{".rel.fini_array", SectionTypeRelData},
};
for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) {
if(cstr_prefix(lookup_sections[i].prefix, name)) {
FURI_LOG_D(TAG, "Found section %s", lookup_sections[i].prefix);
if(lookup_sections[i].type == SectionTypeRelData) {
name = name + strlen(".rel");
}
ELFSection* section_p = elf_file_get_section(elf, name);
if(!section_p) {
ELFSection section = {
.data = NULL,
.sec_idx = 0,
.rel_sec_idx = 0,
.size = 0,
};
elf_file_put_section(elf, name, &section);
section_p = elf_file_get_section(elf, name);
}
if(lookup_sections[i].type == SectionTypeRelData) {
section_p->rel_sec_idx = section_idx;
} else {
section_p->sec_idx = section_idx;
}
return lookup_sections[i].type;
}
}
if(strcmp(name, ".symtab") == 0) {
FURI_LOG_D(TAG, "Found .symtab section");
elf->symbol_table = section_header->sh_offset;
elf->symbol_count = section_header->sh_size / sizeof(Elf32_Sym);
return SectionTypeSymTab;
} else if(strcmp(name, ".strtab") == 0) {
FURI_LOG_D(TAG, "Found .strtab section");
elf->symbol_table_strings = section_header->sh_offset;
return SectionTypeStrTab;
} else if(strcmp(name, ".fapmeta") == 0) {
FURI_LOG_D(TAG, "Found .fapmeta section");
if(elf_load_metadata(elf, section_header, manifest)) {
return SectionTypeManifest;
} else {
return SectionTypeERROR;
}
} else if(strcmp(name, ".gnu_debuglink") == 0) {
FURI_LOG_D(TAG, "Found .gnu_debuglink section");
if(elf_load_debug_link(elf, section_header)) {
return SectionTypeDebugLink;
} else {
return SectionTypeERROR;
}
}
return SectionTypeUnused;
}
static bool elf_load_section_data(ELFFile* elf, ELFSection* section) {
Elf32_Shdr section_header;
if(section->sec_idx == 0) {
FURI_LOG_D(TAG, "Section is not present");
return true;
}
if(!elf_read_section_header(elf, section->sec_idx, &section_header)) {
return false;
}
if(section_header.sh_size == 0) {
FURI_LOG_D(TAG, "No data for section");
return true;
}
section->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign);
section->size = section_header.sh_size;
if(section_header.sh_type == SHT_NOBITS) {
/* section is empty (.bss?) */
/* no need to memset - allocator already did that */
return true;
}
if((!storage_file_seek(elf->fd, section_header.sh_offset, true)) ||
(storage_file_read(elf->fd, section->data, section_header.sh_size) !=
section_header.sh_size)) {
FURI_LOG_E(TAG, " seek/read fail");
return false;
}
FURI_LOG_D(TAG, "0x%X", section->data);
return true;
}
static bool elf_relocate_section(ELFFile* elf, ELFSection* section) {
Elf32_Shdr section_header;
if(section->rel_sec_idx) {
FURI_LOG_D(TAG, "Relocating section");
if(elf_read_section_header(elf, section->rel_sec_idx, &section_header))
return elf_relocate(elf, &section_header, section);
else {
FURI_LOG_E(TAG, "Error reading section header");
return false;
}
} else {
FURI_LOG_D(TAG, "No relocation index"); /* Not an error */
}
return true;
}
static void elf_file_call_section_list(ELFFile* elf, const char* name, bool reverse_order) {
ELFSection* section = elf_file_get_section(elf, name);
if(section && section->size) {
const uint32_t* start = section->data;
const uint32_t* end = section->data + section->size;
if(reverse_order) {
while(end > start) {
end--;
((void (*)(void))(*end))();
}
} else {
while(start < end) {
((void (*)(void))(*start))();
start++;
}
}
}
}
/**************************************************************************************************/
/********************************************* Public *********************************************/
/**************************************************************************************************/
ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface) {
ELFFile* elf = malloc(sizeof(ELFFile));
elf->fd = storage_file_alloc(storage);
elf->api_interface = api_interface;
ELFSectionDict_init(elf->sections);
AddressCache_init(elf->trampoline_cache);
return elf;
}
void elf_file_free(ELFFile* elf) {
// free sections data
{
ELFSectionDict_it_t it;
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
ELFSectionDict_next(it)) {
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
if(itref->value.data) {
aligned_free(itref->value.data);
}
free((void*)itref->key);
}
ELFSectionDict_clear(elf->sections);
}
// free trampoline data
{
AddressCache_it_t it;
for(AddressCache_it(it, elf->trampoline_cache); !AddressCache_end_p(it);
AddressCache_next(it)) {
const AddressCache_itref_t* itref = AddressCache_cref(it);
free((void*)itref->value);
}
AddressCache_clear(elf->trampoline_cache);
}
if(elf->debug_link_info.debug_link) {
free(elf->debug_link_info.debug_link);
}
storage_file_free(elf->fd);
free(elf);
}
bool elf_file_open(ELFFile* elf, const char* path) {
Elf32_Ehdr h;
Elf32_Shdr sH;
if(!storage_file_open(elf->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) ||
!storage_file_seek(elf->fd, 0, true) ||
storage_file_read(elf->fd, &h, sizeof(h)) != sizeof(h) ||
!storage_file_seek(elf->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) ||
storage_file_read(elf->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) {
return false;
}
elf->entry = h.e_entry;
elf->sections_count = h.e_shnum;
elf->section_table = h.e_shoff;
elf->section_table_strings = sH.sh_offset;
return true;
}
bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest) {
bool result = false;
string_t name;
string_init(name);
FURI_LOG_D(TAG, "Looking for manifest section");
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
Elf32_Shdr section_header;
string_reset(name);
if(!elf_read_section(elf, section_idx, &section_header, name)) {
break;
}
if(string_cmp(name, ".fapmeta") == 0) {
if(elf_load_metadata(elf, &section_header, manifest)) {
FURI_LOG_D(TAG, "Load manifest done");
result = true;
break;
} else {
break;
}
}
}
string_clear(name);
return result;
}
bool elf_file_load_section_table(ELFFile* elf, FlipperApplicationManifest* manifest) {
SectionType loaded_sections = SectionTypeERROR;
string_t name;
string_init(name);
FURI_LOG_D(TAG, "Scan ELF indexs...");
for(size_t section_idx = 1; section_idx < elf->sections_count; section_idx++) {
Elf32_Shdr section_header;
string_reset(name);
if(!elf_read_section(elf, section_idx, &section_header, name)) {
loaded_sections = SectionTypeERROR;
break;
}
FURI_LOG_D(TAG, "Preloading data for section #%d %s", section_idx, string_get_cstr(name));
SectionType section_type =
elf_preload_section(elf, section_idx, &section_header, name, manifest);
loaded_sections |= section_type;
if(section_type == SectionTypeERROR) {
loaded_sections = SectionTypeERROR;
break;
}
}
string_clear(name);
FURI_LOG_D(TAG, "Load symbols done");
return IS_FLAGS_SET(loaded_sections, SectionTypeValid);
}
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf) {
ELFFileLoadStatus status = ELFFileLoadStatusSuccess;
ELFSectionDict_it_t it;
AddressCache_init(elf->relocation_cache);
size_t start = furi_get_tick();
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
FURI_LOG_D(TAG, "Loading section '%s'", itref->key);
if(!elf_load_section_data(elf, &itref->value)) {
FURI_LOG_E(TAG, "Error loading section '%s'", itref->key);
status = ELFFileLoadStatusUnspecifiedError;
}
}
if(status == ELFFileLoadStatusSuccess) {
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it);
ELFSectionDict_next(it)) {
ELFSectionDict_itref_t* itref = ELFSectionDict_ref(it);
FURI_LOG_D(TAG, "Relocating section '%s'", itref->key);
if(!elf_relocate_section(elf, &itref->value)) {
FURI_LOG_E(TAG, "Error relocating section '%s'", itref->key);
status = ELFFileLoadStatusMissingImports;
}
}
}
/* Fixing up entry point */
if(status == ELFFileLoadStatusSuccess) {
ELFSection* text_section = elf_file_get_section(elf, ".text");
if(text_section == NULL) {
FURI_LOG_E(TAG, "No .text section found");
status = ELFFileLoadStatusUnspecifiedError;
} else {
elf->entry += (uint32_t)text_section->data;
}
}
FURI_LOG_D(TAG, "Relocation cache size: %u", AddressCache_size(elf->relocation_cache));
FURI_LOG_D(TAG, "Trampoline cache size: %u", AddressCache_size(elf->trampoline_cache));
AddressCache_clear(elf->relocation_cache);
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
return status;
}
void elf_file_pre_run(ELFFile* elf) {
elf_file_call_section_list(elf, ".preinit_array", false);
elf_file_call_section_list(elf, ".init_array", false);
}
int32_t elf_file_run(ELFFile* elf, void* args) {
int32_t result;
result = ((int32_t(*)(void*))elf->entry)(args);
return result;
}
void elf_file_post_run(ELFFile* elf) {
elf_file_call_section_list(elf, ".fini_array", true);
}
const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file) {
return elf_file->api_interface;
}
void elf_file_init_debug_info(ELFFile* elf, ELFDebugInfo* debug_info) {
// set entry
debug_info->entry = elf->entry;
// copy debug info
memcpy(&debug_info->debug_link_info, &elf->debug_link_info, sizeof(ELFDebugLinkInfo));
// init mmap
debug_info->mmap_entry_count = ELFSectionDict_size(elf->sections);
debug_info->mmap_entries = malloc(sizeof(ELFMemoryMapEntry) * debug_info->mmap_entry_count);
uint32_t mmap_entry_idx = 0;
ELFSectionDict_it_t it;
for(ELFSectionDict_it(it, elf->sections); !ELFSectionDict_end_p(it); ELFSectionDict_next(it)) {
const ELFSectionDict_itref_t* itref = ELFSectionDict_cref(it);
const void* data_ptr = itref->value.data;
if(data_ptr) {
debug_info->mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr;
debug_info->mmap_entries[mmap_entry_idx].name = itref->key;
mmap_entry_idx++;
}
}
}
void elf_file_clear_debug_info(ELFDebugInfo* debug_info) {
// clear debug info
memset(&debug_info->debug_link_info, 0, sizeof(ELFDebugLinkInfo));
// clear mmap
if(debug_info->mmap_entries) {
free(debug_info->mmap_entries);
debug_info->mmap_entries = NULL;
}
debug_info->mmap_entry_count = 0;
}

View file

@ -0,0 +1,127 @@
/**
* @file elf_file.h
* ELF file loader
*/
#pragma once
#include <storage/storage.h>
#include "../application_manifest.h"
#include "elf_api_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ELFFile ELFFile;
typedef struct {
const char* name;
uint32_t address;
} ELFMemoryMapEntry;
typedef struct {
uint32_t debug_link_size;
uint8_t* debug_link;
} ELFDebugLinkInfo;
typedef struct {
uint32_t mmap_entry_count;
ELFMemoryMapEntry* mmap_entries;
ELFDebugLinkInfo debug_link_info;
off_t entry;
} ELFDebugInfo;
typedef enum {
ELFFileLoadStatusSuccess = 0,
ELFFileLoadStatusUnspecifiedError,
ELFFileLoadStatusNoFreeMemory,
ELFFileLoadStatusMissingImports,
} ELFFileLoadStatus;
/**
* @brief Allocate ELFFile instance
* @param storage
* @param api_interface
* @return ELFFile*
*/
ELFFile* elf_file_alloc(Storage* storage, const ElfApiInterface* api_interface);
/**
* @brief Free ELFFile instance
* @param elf_file
*/
void elf_file_free(ELFFile* elf_file);
/**
* @brief Open ELF file
* @param elf_file
* @param path
* @return bool
*/
bool elf_file_open(ELFFile* elf_file, const char* path);
/**
* @brief Load ELF file manifest
* @param elf
* @param manifest
* @return bool
*/
bool elf_file_load_manifest(ELFFile* elf, FlipperApplicationManifest* manifest);
/**
* @brief Load ELF file section table (load stage #1)
* @param elf_file
* @param manifest
* @return bool
*/
bool elf_file_load_section_table(ELFFile* elf_file, FlipperApplicationManifest* manifest);
/**
* @brief Load and relocate ELF file sections (load stage #2)
* @param elf_file
* @return ELFFileLoadStatus
*/
ELFFileLoadStatus elf_file_load_sections(ELFFile* elf_file);
/**
* @brief Execute ELF file pre-run stage, call static constructors for example (load stage #3)
* @param elf
*/
void elf_file_pre_run(ELFFile* elf);
/**
* @brief Run ELF file (load stage #4)
* @param elf_file
* @param args
* @return int32_t
*/
int32_t elf_file_run(ELFFile* elf_file, void* args);
/**
* @brief Execute ELF file post-run stage, call static destructors for example (load stage #5)
* @param elf
*/
void elf_file_post_run(ELFFile* elf);
/**
* @brief Get ELF file API interface
* @param elf_file
* @return const ElfApiInterface*
*/
const ElfApiInterface* elf_file_get_api_interface(ELFFile* elf_file);
/**
* @brief Get ELF file debug info
* @param elf_file
* @param debug_info
*/
void elf_file_init_debug_info(ELFFile* elf_file, ELFDebugInfo* debug_info);
/**
* @brief Clear ELF file debug info generated by elf_file_init_debug_info
* @param debug_info
*/
void elf_file_clear_debug_info(ELFDebugInfo* debug_info);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,46 @@
#pragma once
#include "elf_file.h"
#include <m-dict.h>
#ifdef __cplusplus
extern "C" {
#endif
DICT_DEF2(AddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST)
/**
* Callable elf entry type
*/
typedef int32_t(entry_t)(void*);
typedef struct {
void* data;
uint16_t sec_idx;
uint16_t rel_sec_idx;
Elf32_Word size;
} ELFSection;
DICT_DEF2(ELFSectionDict, const char*, M_CSTR_OPLIST, ELFSection, M_POD_OPLIST)
struct ELFFile {
size_t sections_count;
off_t section_table;
off_t section_table_strings;
size_t symbol_count;
off_t symbol_table;
off_t symbol_table_strings;
off_t entry;
ELFSectionDict_t sections;
AddressCache_t relocation_cache;
AddressCache_t trampoline_cache;
File* fd;
const ElfApiInterface* api_interface;
ELFDebugLinkInfo debug_link_info;
};
#ifdef __cplusplus
}
#endif

View file

@ -1,477 +0,0 @@
#include "flipper_application_i.h"
#include <furi.h>
#define TAG "fapp-i"
#define RESOLVER_THREAD_YIELD_STEP 30
#define IS_FLAGS_SET(v, m) ((v & m) == m)
#define SECTION_OFFSET(e, n) (e->section_table + n * sizeof(Elf32_Shdr))
#define SYMBOL_OFFSET(e, n) (e->_table + n * sizeof(Elf32_Shdr))
bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path) {
Elf32_Ehdr h;
Elf32_Shdr sH;
if(!storage_file_open(e->fd, path, FSAM_READ, FSOM_OPEN_EXISTING) ||
!storage_file_seek(e->fd, 0, true) ||
storage_file_read(e->fd, &h, sizeof(h)) != sizeof(h) ||
!storage_file_seek(e->fd, h.e_shoff + h.e_shstrndx * sizeof(sH), true) ||
storage_file_read(e->fd, &sH, sizeof(Elf32_Shdr)) != sizeof(Elf32_Shdr)) {
return false;
}
e->entry = h.e_entry;
e->sections = h.e_shnum;
e->section_table = h.e_shoff;
e->section_table_strings = sH.sh_offset;
return true;
}
static bool flipper_application_load_metadata(FlipperApplication* e, Elf32_Shdr* sh) {
if(sh->sh_size < sizeof(e->manifest)) {
return false;
}
return storage_file_seek(e->fd, sh->sh_offset, true) &&
storage_file_read(e->fd, &e->manifest, sh->sh_size) == sh->sh_size;
}
static bool flipper_application_load_debug_link(FlipperApplication* e, Elf32_Shdr* sh) {
e->state.debug_link_size = sh->sh_size;
e->state.debug_link = malloc(sh->sh_size);
return storage_file_seek(e->fd, sh->sh_offset, true) &&
storage_file_read(e->fd, e->state.debug_link, sh->sh_size) == sh->sh_size;
}
static FindFlags_t flipper_application_preload_section(
FlipperApplication* e,
Elf32_Shdr* sh,
const char* name,
int n) {
FURI_LOG_D(TAG, "Processing: %s", name);
const struct {
const char* name;
uint16_t* ptr_section_idx;
FindFlags_t flags;
} lookup_sections[] = {
{".text", &e->text.sec_idx, FoundText},
{".rodata", &e->rodata.sec_idx, FoundRodata},
{".data", &e->data.sec_idx, FoundData},
{".bss", &e->bss.sec_idx, FoundBss},
{".rel.text", &e->text.rel_sec_idx, FoundRelText},
{".rel.rodata", &e->rodata.rel_sec_idx, FoundRelRodata},
{".rel.data", &e->data.rel_sec_idx, FoundRelData},
};
for(size_t i = 0; i < COUNT_OF(lookup_sections); i++) {
if(strcmp(name, lookup_sections[i].name) == 0) {
*lookup_sections[i].ptr_section_idx = n;
return lookup_sections[i].flags;
}
}
if(strcmp(name, ".symtab") == 0) {
e->symbol_table = sh->sh_offset;
e->symbol_count = sh->sh_size / sizeof(Elf32_Sym);
return FoundSymTab;
} else if(strcmp(name, ".strtab") == 0) {
e->symbol_table_strings = sh->sh_offset;
return FoundStrTab;
} else if(strcmp(name, ".fapmeta") == 0) {
// Load metadata immediately
if(flipper_application_load_metadata(e, sh)) {
return FoundFappManifest;
}
} else if(strcmp(name, ".gnu_debuglink") == 0) {
if(flipper_application_load_debug_link(e, sh)) {
return FoundDebugLink;
}
}
return FoundERROR;
}
static bool
read_string_from_offset(FlipperApplication* e, off_t offset, char* buffer, size_t buffer_size) {
bool success = false;
off_t old = storage_file_tell(e->fd);
if(storage_file_seek(e->fd, offset, true) &&
(storage_file_read(e->fd, buffer, buffer_size) == buffer_size)) {
success = true;
}
storage_file_seek(e->fd, old, true);
return success;
}
static bool read_section_name(FlipperApplication* e, off_t off, char* buf, size_t max) {
return read_string_from_offset(e, e->section_table_strings + off, buf, max);
}
static bool read_symbol_name(FlipperApplication* e, off_t off, char* buf, size_t max) {
return read_string_from_offset(e, e->symbol_table_strings + off, buf, max);
}
static bool read_section_header(FlipperApplication* e, int n, Elf32_Shdr* h) {
off_t offset = SECTION_OFFSET(e, n);
return storage_file_seek(e->fd, offset, true) &&
storage_file_read(e->fd, h, sizeof(Elf32_Shdr)) == sizeof(Elf32_Shdr);
}
static bool read_section(FlipperApplication* e, int n, Elf32_Shdr* h, char* name, size_t nlen) {
if(!read_section_header(e, n, h)) {
return false;
}
if(!h->sh_name) {
return true;
}
return read_section_name(e, h->sh_name, name, nlen);
}
bool flipper_application_load_section_table(FlipperApplication* e) {
furi_check(e->state.mmap_entry_count == 0);
size_t n;
FindFlags_t found = FoundERROR;
FURI_LOG_D(TAG, "Scan ELF indexs...");
for(n = 1; n < e->sections; n++) {
Elf32_Shdr section_header;
char name[33] = {0};
if(!read_section_header(e, n, &section_header)) {
return false;
}
if(section_header.sh_name &&
!read_section_name(e, section_header.sh_name, name, sizeof(name))) {
return false;
}
FURI_LOG_T(TAG, "Examining section %d %s", n, name);
FindFlags_t section_flags =
flipper_application_preload_section(e, &section_header, name, n);
found |= section_flags;
if((section_flags & FoundGdbSection) != 0) {
e->state.mmap_entry_count++;
}
if(IS_FLAGS_SET(found, FoundAll)) {
return true;
}
}
FURI_LOG_D(TAG, "Load symbols done");
return IS_FLAGS_SET(found, FoundValid);
}
static const char* type_to_str(int symt) {
#define STRCASE(name) \
case name: \
return #name;
switch(symt) {
STRCASE(R_ARM_NONE)
STRCASE(R_ARM_ABS32)
STRCASE(R_ARM_THM_PC22)
STRCASE(R_ARM_THM_JUMP24)
default:
return "R_<unknow>";
}
#undef STRCASE
}
static void relocate_jmp_call(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
UNUSED(type);
uint16_t upper_insn = ((uint16_t*)relAddr)[0];
uint16_t lower_insn = ((uint16_t*)relAddr)[1];
uint32_t S = (upper_insn >> 10) & 1;
uint32_t J1 = (lower_insn >> 13) & 1;
uint32_t J2 = (lower_insn >> 11) & 1;
int32_t offset = (S << 24) | /* S -> offset[24] */
((~(J1 ^ S) & 1) << 23) | /* J1 -> offset[23] */
((~(J2 ^ S) & 1) << 22) | /* J2 -> offset[22] */
((upper_insn & 0x03ff) << 12) | /* imm10 -> offset[12:21] */
((lower_insn & 0x07ff) << 1); /* imm11 -> offset[1:11] */
if(offset & 0x01000000) offset -= 0x02000000;
offset += symAddr - relAddr;
S = (offset >> 24) & 1;
J1 = S ^ (~(offset >> 23) & 1);
J2 = S ^ (~(offset >> 22) & 1);
upper_insn = ((upper_insn & 0xf800) | (S << 10) | ((offset >> 12) & 0x03ff));
((uint16_t*)relAddr)[0] = upper_insn;
lower_insn = ((lower_insn & 0xd000) | (J1 << 13) | (J2 << 11) | ((offset >> 1) & 0x07ff));
((uint16_t*)relAddr)[1] = lower_insn;
}
static bool relocate_symbol(Elf32_Addr relAddr, int type, Elf32_Addr symAddr) {
switch(type) {
case R_ARM_ABS32:
*((uint32_t*)relAddr) += symAddr;
FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
break;
case R_ARM_THM_PC22:
case R_ARM_THM_JUMP24:
relocate_jmp_call(relAddr, type, symAddr);
FURI_LOG_D(
TAG, " R_ARM_THM_CALL/JMP relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr));
break;
default:
FURI_LOG_D(TAG, " Undefined relocation %d", type);
return false;
}
return true;
}
static ELFSection_t* section_of(FlipperApplication* e, int index) {
if(e->text.sec_idx == index) {
return &e->text;
} else if(e->data.sec_idx == index) {
return &e->data;
} else if(e->bss.sec_idx == index) {
return &e->bss;
} else if(e->rodata.sec_idx == index) {
return &e->rodata;
}
return NULL;
}
static Elf32_Addr address_of(FlipperApplication* e, Elf32_Sym* sym, const char* sName) {
if(sym->st_shndx == SHN_UNDEF) {
Elf32_Addr addr = 0;
if(e->api_interface->resolver_callback(sName, &addr)) {
return addr;
}
} else {
ELFSection_t* symSec = section_of(e, sym->st_shndx);
if(symSec) {
return ((Elf32_Addr)symSec->data) + sym->st_value;
}
}
FURI_LOG_D(TAG, " Can not find address for symbol %s", sName);
return ELF_INVALID_ADDRESS;
}
static bool read_symbol(FlipperApplication* e, int n, Elf32_Sym* sym, char* name, size_t nlen) {
bool success = false;
off_t old = storage_file_tell(e->fd);
off_t pos = e->symbol_table + n * sizeof(Elf32_Sym);
if(storage_file_seek(e->fd, pos, true) &&
storage_file_read(e->fd, sym, sizeof(Elf32_Sym)) == sizeof(Elf32_Sym)) {
if(sym->st_name)
success = read_symbol_name(e, sym->st_name, name, nlen);
else {
Elf32_Shdr shdr;
success = read_section(e, sym->st_shndx, &shdr, name, nlen);
}
}
storage_file_seek(e->fd, old, true);
return success;
}
static bool
relocation_cache_get(RelocationAddressCache_t cache, int symEntry, Elf32_Addr* symAddr) {
Elf32_Addr* addr = RelocationAddressCache_get(cache, symEntry);
if(addr) {
*symAddr = *addr;
return true;
} else {
return false;
}
}
static void
relocation_cache_put(RelocationAddressCache_t cache, int symEntry, Elf32_Addr symAddr) {
RelocationAddressCache_set_at(cache, symEntry, symAddr);
}
#define MAX_SYMBOL_NAME_LEN 128u
static bool relocate(FlipperApplication* e, Elf32_Shdr* h, ELFSection_t* s) {
if(s->data) {
Elf32_Rel rel;
size_t relEntries = h->sh_size / sizeof(rel);
size_t relCount;
(void)storage_file_seek(e->fd, h->sh_offset, true);
FURI_LOG_D(TAG, " Offset Info Type Name");
int relocate_result = true;
char symbol_name[MAX_SYMBOL_NAME_LEN + 1] = {0};
for(relCount = 0; relCount < relEntries; relCount++) {
if(relCount % RESOLVER_THREAD_YIELD_STEP == 0) {
FURI_LOG_D(TAG, " reloc YIELD");
furi_delay_tick(1);
}
if(storage_file_read(e->fd, &rel, sizeof(Elf32_Rel)) != sizeof(Elf32_Rel)) {
FURI_LOG_E(TAG, " reloc read fail");
return false;
}
Elf32_Addr symAddr;
int symEntry = ELF32_R_SYM(rel.r_info);
int relType = ELF32_R_TYPE(rel.r_info);
Elf32_Addr relAddr = ((Elf32_Addr)s->data) + rel.r_offset;
if(!relocation_cache_get(e->relocation_cache, symEntry, &symAddr)) {
Elf32_Sym sym;
if(!read_symbol(e, symEntry, &sym, symbol_name, MAX_SYMBOL_NAME_LEN)) {
FURI_LOG_E(TAG, " symbol read fail");
return false;
}
FURI_LOG_D(
TAG,
" %08X %08X %-16s %s",
(unsigned int)rel.r_offset,
(unsigned int)rel.r_info,
type_to_str(relType),
symbol_name);
symAddr = address_of(e, &sym, symbol_name);
relocation_cache_put(e->relocation_cache, symEntry, symAddr);
}
if(symAddr != ELF_INVALID_ADDRESS) {
FURI_LOG_D(
TAG,
" symAddr=%08X relAddr=%08X",
(unsigned int)symAddr,
(unsigned int)relAddr);
if(!relocate_symbol(relAddr, relType, symAddr)) {
relocate_result = false;
}
} else {
FURI_LOG_D(TAG, " No symbol address of %s", symbol_name);
relocate_result = false;
}
}
return relocate_result;
} else
FURI_LOG_I(TAG, "Section not loaded");
return false;
}
static bool flipper_application_load_section_data(FlipperApplication* e, ELFSection_t* s) {
Elf32_Shdr section_header;
if(s->sec_idx == 0) {
FURI_LOG_I(TAG, "Section is not present");
return true;
}
if(!read_section_header(e, s->sec_idx, &section_header)) {
return false;
}
if(section_header.sh_size == 0) {
FURI_LOG_I(TAG, "No data for section");
return true;
}
s->data = aligned_malloc(section_header.sh_size, section_header.sh_addralign);
// e->state.mmap_entry_count++;
if(section_header.sh_type == SHT_NOBITS) {
/* section is empty (.bss?) */
/* no need to memset - allocator already did that */
/* memset(s->data, 0, h->sh_size); */
FURI_LOG_D(TAG, "0x%X", s->data);
return true;
}
if((!storage_file_seek(e->fd, section_header.sh_offset, true)) ||
(storage_file_read(e->fd, s->data, section_header.sh_size) != section_header.sh_size)) {
FURI_LOG_E(TAG, " seek/read fail");
flipper_application_free_section(s);
return false;
}
FURI_LOG_D(TAG, "0x%X", s->data);
return true;
}
static bool flipper_application_relocate_section(FlipperApplication* e, ELFSection_t* s) {
Elf32_Shdr section_header;
if(s->rel_sec_idx) {
FURI_LOG_D(TAG, "Relocating section");
if(read_section_header(e, s->rel_sec_idx, &section_header))
return relocate(e, &section_header, s);
else {
FURI_LOG_E(TAG, "Error reading section header");
return false;
}
} else
FURI_LOG_D(TAG, "No relocation index"); /* Not an error */
return true;
}
FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e) {
FlipperApplicationLoadStatus status = FlipperApplicationLoadStatusSuccess;
RelocationAddressCache_init(e->relocation_cache);
size_t start = furi_get_tick();
struct {
ELFSection_t* section;
const char* name;
} sections[] = {
{&e->text, ".text"},
{&e->rodata, ".rodata"},
{&e->data, ".data"},
{&e->bss, ".bss"},
};
for(size_t i = 0; i < COUNT_OF(sections); i++) {
if(!flipper_application_load_section_data(e, sections[i].section)) {
FURI_LOG_E(TAG, "Error loading section '%s'", sections[i].name);
status = FlipperApplicationLoadStatusUnspecifiedError;
}
}
if(status == FlipperApplicationLoadStatusSuccess) {
for(size_t i = 0; i < COUNT_OF(sections); i++) {
if(!flipper_application_relocate_section(e, sections[i].section)) {
FURI_LOG_E(TAG, "Error relocating section '%s'", sections[i].name);
status = FlipperApplicationLoadStatusMissingImports;
}
}
}
if(status == FlipperApplicationLoadStatusSuccess) {
e->state.mmap_entries =
malloc(sizeof(FlipperApplicationMemoryMapEntry) * e->state.mmap_entry_count);
uint32_t mmap_entry_idx = 0;
for(size_t i = 0; i < COUNT_OF(sections); i++) {
const void* data_ptr = sections[i].section->data;
if(data_ptr) {
FURI_LOG_I(TAG, "0x%X %s", (uint32_t)data_ptr, sections[i].name);
e->state.mmap_entries[mmap_entry_idx].address = (uint32_t)data_ptr;
e->state.mmap_entries[mmap_entry_idx].name = sections[i].name;
mmap_entry_idx++;
}
}
furi_check(mmap_entry_idx == e->state.mmap_entry_count);
/* Fixing up entry point */
e->entry += (uint32_t)e->text.data;
}
FURI_LOG_D(TAG, "Relocation cache size: %u", RelocationAddressCache_size(e->relocation_cache));
RelocationAddressCache_clear(e->relocation_cache);
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
return status;
}
void flipper_application_free_section(ELFSection_t* s) {
if(s->data) {
aligned_free(s->data);
}
s->data = NULL;
}

View file

@ -1,16 +1,22 @@
#include "flipper_application.h"
#include "flipper_application_i.h"
#include "elf/elf_file.h"
#define TAG "fapp"
struct FlipperApplication {
ELFDebugInfo state;
FlipperApplicationManifest manifest;
ELFFile* elf;
FuriThread* thread;
};
/* For debugger access to app state */
FlipperApplication* last_loaded_app = NULL;
FlipperApplication*
flipper_application_alloc(Storage* storage, const ElfApiInterface* api_interface) {
FlipperApplication* app = malloc(sizeof(FlipperApplication));
app->api_interface = api_interface;
app->fd = storage_file_alloc(storage);
app->elf = elf_file_alloc(storage, api_interface);
app->thread = NULL;
return app;
}
@ -25,56 +31,71 @@ void flipper_application_free(FlipperApplication* app) {
last_loaded_app = NULL;
if(app->state.debug_link_size) {
free(app->state.debug_link);
}
if(app->state.mmap_entries) {
free(app->state.mmap_entries);
}
ELFSection_t* sections[] = {&app->text, &app->rodata, &app->data, &app->bss};
for(size_t i = 0; i < COUNT_OF(sections); i++) {
flipper_application_free_section(sections[i]);
}
storage_file_free(app->fd);
elf_file_clear_debug_info(&app->state);
elf_file_free(app->elf);
free(app);
}
/* Parse headers, load manifest */
FlipperApplicationPreloadStatus
flipper_application_preload(FlipperApplication* app, const char* path) {
if(!flipper_application_load_elf_headers(app, path) ||
!flipper_application_load_section_table(app)) {
return FlipperApplicationPreloadStatusInvalidFile;
}
if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) &&
(app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) {
static FlipperApplicationPreloadStatus
flipper_application_validate_manifest(FlipperApplication* app) {
if(!flipper_application_manifest_is_valid(&app->manifest)) {
return FlipperApplicationPreloadStatusInvalidManifest;
}
if(app->manifest.base.api_version.major != app->api_interface->api_version_major /* ||
app->manifest.base.api_version.minor > app->api_interface->api_version_minor */) {
if(!flipper_application_manifest_is_compatible(
&app->manifest, elf_file_get_api_interface(app->elf))) {
return FlipperApplicationPreloadStatusApiMismatch;
}
return FlipperApplicationPreloadStatusSuccess;
}
/* Parse headers, load manifest */
FlipperApplicationPreloadStatus
flipper_application_preload_manifest(FlipperApplication* app, const char* path) {
if(!elf_file_open(app->elf, path) || !elf_file_load_manifest(app->elf, &app->manifest)) {
return FlipperApplicationPreloadStatusInvalidFile;
}
return flipper_application_validate_manifest(app);
}
/* Parse headers, load full file */
FlipperApplicationPreloadStatus
flipper_application_preload(FlipperApplication* app, const char* path) {
if(!elf_file_open(app->elf, path) || !elf_file_load_section_table(app->elf, &app->manifest)) {
return FlipperApplicationPreloadStatusInvalidFile;
}
return flipper_application_validate_manifest(app);
}
const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
return &app->manifest;
}
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app) {
last_loaded_app = app;
return flipper_application_load_sections(app);
ELFFileLoadStatus status = elf_file_load_sections(app->elf);
switch(status) {
case ELFFileLoadStatusSuccess:
elf_file_init_debug_info(app->elf, &app->state);
return FlipperApplicationLoadStatusSuccess;
case ELFFileLoadStatusNoFreeMemory:
return FlipperApplicationLoadStatusNoFreeMemory;
case ELFFileLoadStatusMissingImports:
return FlipperApplicationLoadStatusMissingImports;
default:
return FlipperApplicationLoadStatusUnspecifiedError;
}
}
const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app) {
return &app->state;
static int32_t flipper_application_thread(void* context) {
elf_file_pre_run(last_loaded_app->elf);
int32_t result = elf_file_run(last_loaded_app->elf, context);
elf_file_post_run(last_loaded_app->elf);
return result;
}
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
@ -86,20 +107,12 @@ FuriThread* flipper_application_spawn(FlipperApplication* app, void* args) {
app->thread = furi_thread_alloc();
furi_thread_set_stack_size(app->thread, manifest->stack_size);
furi_thread_set_name(app->thread, manifest->name);
furi_thread_set_callback(app->thread, (entry_t*)app->entry);
furi_thread_set_callback(app->thread, flipper_application_thread);
furi_thread_set_context(app->thread, args);
return app->thread;
}
FuriThread* flipper_application_get_thread(FlipperApplication* app) {
return app->thread;
}
void const* flipper_application_get_entry_address(FlipperApplication* app) {
return (void*)app->entry;
}
static const char* preload_status_strings[] = {
[FlipperApplicationPreloadStatusSuccess] = "Success",
[FlipperApplicationPreloadStatusUnspecifiedError] = "Unknown error",

View file

@ -1,3 +1,7 @@
/**
* @file flipper_application.h
* Flipper application
*/
#pragma once
#include "application_manifest.h"
@ -79,6 +83,14 @@ void flipper_application_free(FlipperApplication* app);
FlipperApplicationPreloadStatus
flipper_application_preload(FlipperApplication* app, const char* path);
/**
* @brief Validate elf file and load application manifest
* @param app Application pointer
* @return Preload result code
*/
FlipperApplicationPreloadStatus
flipper_application_preload_manifest(FlipperApplication* app, const char* path);
/**
* @brief Get pointer to application manifest for preloaded application
* @param app Application pointer
@ -93,13 +105,6 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic
*/
FlipperApplicationLoadStatus flipper_application_map_to_memory(FlipperApplication* app);
/**
* @brief Get state object for loaded application
* @param app Application pointer
* @return Pointer to state object
*/
const FlipperApplicationState* flipper_application_get_state(FlipperApplication* app);
/**
* @brief Create application thread at entry point address, using app name and
* stack size from metadata. Returned thread isn't started yet.
@ -110,20 +115,6 @@ const FlipperApplicationState* flipper_application_get_state(FlipperApplication*
*/
FuriThread* flipper_application_spawn(FlipperApplication* app, void* args);
/**
* @brief Get previously spawned thread
* @param app Application pointer
* @return Created thread
*/
FuriThread* flipper_application_get_thread(FlipperApplication* app);
/**
* @brief Return relocated and valid address of app's entry point
* @param app Application pointer
* @return Address of app's entry point
*/
void const* flipper_application_get_entry_address(FlipperApplication* app);
#ifdef __cplusplus
}
#endif

View file

@ -1,99 +0,0 @@
#pragma once
#include "elf.h"
#include "flipper_application.h"
#include <m-dict.h>
#ifdef __cplusplus
extern "C" {
#endif
DICT_DEF2(RelocationAddressCache, int, M_DEFAULT_OPLIST, Elf32_Addr, M_DEFAULT_OPLIST)
/**
* Callable elf entry type
*/
typedef int32_t(entry_t)(void*);
typedef struct {
void* data;
uint16_t sec_idx;
uint16_t rel_sec_idx;
} ELFSection_t;
struct FlipperApplication {
const ElfApiInterface* api_interface;
File* fd;
FlipperApplicationState state;
FlipperApplicationManifest manifest;
size_t sections;
off_t section_table;
off_t section_table_strings;
size_t symbol_count;
off_t symbol_table;
off_t symbol_table_strings;
off_t entry;
ELFSection_t text;
ELFSection_t rodata;
ELFSection_t data;
ELFSection_t bss;
FuriThread* thread;
RelocationAddressCache_t relocation_cache;
};
typedef enum {
FoundERROR = 0,
FoundSymTab = (1 << 0),
FoundStrTab = (1 << 2),
FoundText = (1 << 3),
FoundRodata = (1 << 4),
FoundData = (1 << 5),
FoundBss = (1 << 6),
FoundRelText = (1 << 7),
FoundRelRodata = (1 << 8),
FoundRelData = (1 << 9),
FoundRelBss = (1 << 10),
FoundFappManifest = (1 << 11),
FoundDebugLink = (1 << 12),
FoundValid = FoundSymTab | FoundStrTab | FoundFappManifest,
FoundExec = FoundValid | FoundText,
FoundGdbSection = FoundText | FoundRodata | FoundData | FoundBss,
FoundAll = FoundSymTab | FoundStrTab | FoundText | FoundRodata | FoundData | FoundBss |
FoundRelText | FoundRelRodata | FoundRelData | FoundRelBss | FoundDebugLink,
} FindFlags_t;
/**
* @brief Load and validate basic ELF file headers
* @param e Application instance
* @param path FS path to application file
* @return true if ELF file is valid
*/
bool flipper_application_load_elf_headers(FlipperApplication* e, const char* path);
/**
* @brief Iterate over all sections and save related indexes
* @param e Application instance
* @return true if all required sections are found
*/
bool flipper_application_load_section_table(FlipperApplication* e);
/**
* @brief Load section data to memory and process relocations
* @param e Application instance
* @return Status code
*/
FlipperApplicationLoadStatus flipper_application_load_sections(FlipperApplication* e);
/**
* @brief Release section data
* @param s section pointer
*/
void flipper_application_free_section(ELFSection_t* s);
#ifdef __cplusplus
}
#endif