Jelajahi Sumber

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>
Sergey Gavrilov 3 tahun lalu
induk
melakukan
e6d22ed147

+ 1 - 1
applications/main/fap_loader/fap_loader_app.c

@@ -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;
 

+ 4 - 4
debug/flipperapps.py

@@ -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(

+ 4 - 0
debug/stm32wbx.cfg

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

+ 4 - 4
firmware/targets/f7/api_symbols.csv

@@ -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*

+ 21 - 0
lib/flipper_application/application_manifest.c

@@ -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;
+}

+ 25 - 0
lib/flipper_application/application_manifest.h

@@ -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

+ 1 - 1
lib/flipper_application/elf/elf_api_interface.h

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

+ 794 - 0
lib/flipper_application/elf/elf_file.c

@@ -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;
+}

+ 127 - 0
lib/flipper_application/elf/elf_file.h

@@ -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

+ 46 - 0
lib/flipper_application/elf/elf_file_i.h

@@ -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

+ 0 - 477
lib/flipper_application/flipper_applicaiton_i.c

@@ -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;
-}

+ 50 - 37
lib/flipper_application/flipper_application.c

@@ -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,43 +31,43 @@ void flipper_application_free(FlipperApplication* app) {
 
     last_loaded_app = NULL;
 
-    if(app->state.debug_link_size) {
-        free(app->state.debug_link);
-    }
+    elf_file_clear_debug_info(&app->state);
+    elf_file_free(app->elf);
+    free(app);
+}
 
-    if(app->state.mmap_entries) {
-        free(app->state.mmap_entries);
+static FlipperApplicationPreloadStatus
+    flipper_application_validate_manifest(FlipperApplication* app) {
+    if(!flipper_application_manifest_is_valid(&app->manifest)) {
+        return FlipperApplicationPreloadStatusInvalidManifest;
     }
 
-    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]);
+    if(!flipper_application_manifest_is_compatible(
+           &app->manifest, elf_file_get_api_interface(app->elf))) {
+        return FlipperApplicationPreloadStatusApiMismatch;
     }
 
-    storage_file_free(app->fd);
-
-    free(app);
+    return FlipperApplicationPreloadStatusSuccess;
 }
 
 /* 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)) {
+    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;
     }
 
-    if((app->manifest.base.manifest_magic != FAP_MANIFEST_MAGIC) &&
-       (app->manifest.base.manifest_version == FAP_MANIFEST_SUPPORTED_VERSION)) {
-        return FlipperApplicationPreloadStatusInvalidManifest;
-    }
+    return flipper_application_validate_manifest(app);
+}
 
-    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 */) {
-        return FlipperApplicationPreloadStatusApiMismatch;
+/* 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 FlipperApplicationPreloadStatusSuccess;
+    return flipper_application_validate_manifest(app);
 }
 
 const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplication* app) {
@@ -70,11 +76,26 @@ const FlipperApplicationManifest* flipper_application_get_manifest(FlipperApplic
 
 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",

+ 12 - 21
lib/flipper_application/flipper_application.h

@@ -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

+ 0 - 99
lib/flipper_application/flipper_application_i.h

@@ -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