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:
		
							parent
							
								
									7e2008095e
								
							
						
					
					
						commit
						e6d22ed147
					
				| @ -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; | ||||
| 
 | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -101,3 +101,7 @@ $_TARGETNAME configure -event trace-config { | ||||
|     # assignment | ||||
|     mmw 0xE0042004 0x00000020 0 | ||||
| } | ||||
| 
 | ||||
| $_TARGETNAME configure -event gdb-detach { | ||||
|     resume | ||||
| } | ||||
| @ -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
									
								
								lib/flipper_application/application_manifest.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/flipper_application/application_manifest.c
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
| @ -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,6 +1,6 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <flipper_application/elf/elf.h> | ||||
| #include <elf.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #define ELF_INVALID_ADDRESS 0xFFFFFFFF | ||||
|  | ||||
							
								
								
									
										794
									
								
								lib/flipper_application/elf/elf_file.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										794
									
								
								lib/flipper_application/elf/elf_file.c
									
									
									
									
									
										Normal 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, §ion); | ||||
|                 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, §ion_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, §ion_header)) | ||||
|             return elf_relocate(elf, §ion_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, §ion_header, name)) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if(string_cmp(name, ".fapmeta") == 0) { | ||||
|             if(elf_load_metadata(elf, §ion_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, §ion_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, §ion_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
									
								
								lib/flipper_application/elf/elf_file.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								lib/flipper_application/elf/elf_file.h
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										46
									
								
								lib/flipper_application/elf/elf_file_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/flipper_application/elf/elf_file_i.h
									
									
									
									
									
										Normal 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 | ||||
| @ -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, §ion_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, §ion_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, §ion_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, §ion_header)) | ||||
|             return relocate(e, §ion_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; | ||||
| } | ||||
| @ -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", | ||||
|  | ||||
| @ -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 | ||||
| @ -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 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sergey Gavrilov
						Sergey Gavrilov