Compare commits

...

2 Commits

Author SHA1 Message Date
Ken Van Hoeylandt
7a81b525ed
elf_loader update and fix for remote install (#641) 2026-08-30 12:58:48 +02:00
Ken Van Hoeylandt
6e5e35610b
Implement app streams: stdio for apps (#640)
Implement file IO for applications to facilitate text input/output capturing.

Apps can now launch apps and:
- Read their stdio output
- Write to their stdio input
2026-08-30 12:07:40 +02:00
45 changed files with 1551 additions and 2707 deletions

View File

@ -125,6 +125,11 @@ def main():
print("Example: release-sdk.py release/TactilitySDK")
sys.exit(1)
esp_idf_version = os.environ.get("ESP_IDF_VERSION", "")
if not esp_idf_version:
print("Error: ESP_IDF_VERSION environment variable is not set")
sys.exit(1)
target_path = os.path.abspath(sys.argv[1])
os.makedirs(target_path, exist_ok=True)
@ -148,8 +153,9 @@ def main():
{'src': 'Libraries/lvgl/src/lv_conf_kconfig.h', 'dst': 'Libraries/lvgl/include/lv_conf.h'},
{'src': 'Libraries/lvgl/src/**/*.h', 'dst': 'Libraries/lvgl/include/src/'},
# elf_loader
{'src': 'Libraries/elf_loader/elf_loader.cmake', 'dst': 'Libraries/elf_loader/'},
{'src': 'Libraries/elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'},
{'src': 'managed_components/espressif__elf_loader/*.cmake', 'dst': 'Libraries/elf_loader/'},
{'src': 'managed_components/espressif__elf_loader/*.lf', 'dst': 'Libraries/elf_loader/'},
{'src': 'managed_components/espressif__elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'},
# minitar
{'src': 'build/esp-idf/minitar/libminitar.a', 'dst': 'Libraries/minitar/binary/'},
{'src': 'Libraries/minitar/minitar/minitar.h', 'dst': 'Libraries/minitar/include/'},
@ -179,7 +185,6 @@ def main():
generate_tactility_sdk_top_cmakelists(target_path)
# Output ESP-IDF SDK version to file
esp_idf_version = os.environ.get("ESP_IDF_VERSION", "")
with open(os.path.join(target_path, "idf-version.txt"), "a") as f:
f.write(esp_idf_version)

View File

@ -49,7 +49,6 @@ if (DEFINED ENV{ESP_IDF_VERSION})
"TactilityKernel"
"TactilityKernelCpp"
"TactilityFreeRtos"
"Libraries/elf_loader"
"Libraries/esp_epaper"
"Libraries/lv_screenshot"
"Libraries/minitar"
@ -64,6 +63,10 @@ if (DEFINED ENV{ESP_IDF_VERSION})
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND)
endif ()
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=read" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=write" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=close" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_button_create" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_dropdown_create" APPEND)
idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_list_create" APPEND)

View File

@ -1,22 +0,0 @@
# ChangeLog
## v1.1.1 - 2025-06-26
* Added support for ESP32-C61
## v1.1.0 - 2025-05-06
* Added fast build for ELF application
* Added a script to generate the symbol table for the ELF APP:
* Supports generating symbols table based on ELF file
* Supports generating symbols table based on static libraries
## v1.0.0 - 2024-12-09
* Added support for the following RISC-V chips: ESP32-P4 and ESP32-C6
* Added support for linking other components to ELF file
* Fixed the issue of getting wrong symbol type
## v0.1.0 - 2023-08-14
* Add basic ELF loader component

View File

@ -1,40 +0,0 @@
if(CONFIG_ELF_LOADER)
set(srcs "src/esp_elf_symbol.c"
"src/esp_elf.c"
"src/esp_elf_adapter.c")
if(CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS)
list(APPEND srcs "src/esp_all_symbol.c")
endif()
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
list(APPEND srcs "src/arch/esp_elf_xtensa.c")
# ESP32-S2 need to set MMU to run ELF
if(CONFIG_IDF_TARGET_ESP32S2 AND (CONFIG_ELF_LOADER_LOAD_PSRAM))
list(APPEND srcs "src/soc/esp_elf_esp32s2.c")
endif()
elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND srcs "src/arch/esp_elf_riscv.c")
endif()
set(include_dirs "include")
set(ldfragments "linker.lf")
endif()
if(CONFIG_IDF_TARGET_ESP32P4)
set(priv_req spi_flash esp_mm)
else()
set(priv_req spi_flash)
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${include_dirs}
PRIV_REQUIRES spi_flash ${priv_req}
LDFRAGMENTS ${ldfragments})
include(package_manager)
if(CONFIG_ELF_LOADER)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})
endif()

View File

@ -1,53 +0,0 @@
menu "Espressif ELF Loader Configuration"
visible if (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61)
config ELF_LOADER_BUS_ADDRESS_MIRROR
bool
default y if (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3)
default n if (IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61)
config ELF_LOADER
bool "Enable Espressif ELF Loader"
default y
depends on (IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61)
help
Select this option to enable ELF Loader and show the submenu with ELF Loader configuration choices.
if ELF_LOADER
config ELF_LOADER_CACHE_OFFSET
bool
default n
help
Select this option if D-cache and I-cache has different offset to access the same physical address.
config ELF_LOADER_SET_MMU
bool
default n
help
Select this option if D-cache and I-cache is not symmetry。
config ELF_LOADER_LOAD_PSRAM
bool "Load ELF to PSRAM"
default y
depends on (IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4 || IDF_TARGET_ESP32C61) && SPIRAM
select ELF_LOADER_CACHE_OFFSET if (IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3)
select ELF_LOADER_SET_MMU if IDF_TARGET_ESP32S2
help
Load ELF file into PSRAM instead of internal SRAM.
menu "ELF Symbols Table"
config ELF_LOADER_LIBC_SYMBOLS
bool "Libc Symbols Table"
default y
config ELF_LOADER_ESPIDF_SYMBOLS
bool "ESP-IDF Symbols Table"
default y
config ELF_LOADER_CUSTOMER_SYMBOLS
bool "Customer Symbols Table"
default n
endmenu
endif
endmenu

View File

@ -1,109 +0,0 @@
## Description
[![Component Registry](https://components.espressif.com/components/espressif/elf_loader/badge.svg)](https://components.espressif.com/components/espressif/elf_loader)
Espressif ELF(Executable and Linkable Format) loader is a software development kit that is developed based on the ESP-IDF, mainly used to load ELF file compiled based on ESP32 series SoCs to the executable memory area, then link and execute it.
In this way, the application does not need compile into the whole firmware in advance. It is like running the compiled program through terminal input `./main.o` on the Ubuntu platform automatically, which realizes the separation of application and kernel.
This ELF loader supports following SoCs:
- ESP32
- ESP32-S2, support running ELF in PSRAM
- ESP32-S3, support running ELF in PSRAM
- ESP32-P4, support running ELF in PSRAM
- ESP32-C6
- ESP32-C61, support running ELF in PSRAM
### Usage
#### Firmware
Add a dependency on this component in your component or project's idf_component.yml file.
```yml
dependencies:
espressif/elf_loader: "1.*"
```
Enable ELF loader in the menuconfig:
```
Component config --->
ESP-ELFLoader Configuration --->
[*] Enable Espressif ELF Loader
```
Add API calls in your project as follows:
```c
#include "esp_elf.h"
esp_elf_t elf;
// Your Code
esp_elf_init(&elf);
esp_elf_relocate(&elf, elf_file_data_bytes);
esp_elf_request(&elf, 0, argc, argv);
esp_elf_deinit(&elf);
```
#### ELF APP
To use this feature to compile ELF file, including the required CMake file in your project's CMakeLists.txt file after the line project(XXXX).
```cmake
project(XXXX)
# Add
include(elf_loader)
project_elf(XXXX)
```
Build the project as an ordinary ESP-IDF project, and then the ELF file named `XXXX.app.elf` is in the build directory.
### ELF APP Fast Build
Users can enable ELF fast build functionality by configuring CMAKE's generator as Unit Makefile. The reference command is as follows:
```bash
idf.py -G 'Unix Makefiles' set-target <chip-name>
```
Then input the ELF APP build command as follows:
```
idf.py elf
```
The build system will only build ELF target components and show the following logs:
```
Building C object esp-idf/main/CMakeFiles/__idf_main.dir/main.c.obj
Linking C static library libmain.a
Build ELF: hello_world.app.elf
Built target elf
```
### Adding the Component to Your Project
Please use the component manager command add-dependency to add the elf_loader component as a dependency in your project. During the CMake step, the component will be downloaded automatically.
```
idf.py add-dependency "espressif/elf_loader=*"
```
### Examples
Please use the component manager command create-project-from-example to create a project from the example template.
```
idf.py create-project-from-example "espressif/elf_loader=*:elf_loader_example"
```
This command will download the example elf_loader_example into the current folder. You can navigate into it to build and flash the example.
Alternatively, you can download examples from the esp-iot-solution repository:
1. [build_elf_file_example](https://github.com/espressif/esp-iot-solution/tree/master/examples/elf_loader/build_elf_file_example)
2. [elf_loader_example](https://github.com/espressif/esp-iot-solution/tree/master/examples/elf_loader/elf_loader_example)

View File

@ -1,79 +0,0 @@
# The script is to generate ELF for application
# Trick to temporarily redefine project(). When functions are overridden in CMake, the originals can still be accessed
# using an underscore prefixed function of the same name. The following lines make sure that project calls
# the original project(). See https://cmake.org/pipermail/cmake/2015-October/061751.html.
function(project_elf)
endfunction()
function(_project_elf)
endfunction()
macro(project_elf project_name)
# Enable these options to remove unused symbols and reduce linked objects
set(cflags -nostartfiles
-nostdlib
-fPIC
-shared
-e app_main
-fdata-sections
-ffunction-sections
-Wl,--gc-sections
-fvisibility=hidden)
# Enable this options to remove unnecessary sections in
list(APPEND cflags -Wl,--strip-all
-Wl,--strip-debug
-Wl,--strip-discarded)
list(APPEND cflags -Dmain=app_main)
idf_build_set_property(COMPILE_OPTIONS "${cflags}" APPEND)
set(elf_app "${CMAKE_PROJECT_NAME}.app.elf")
# Remove more unused sections
string(REPLACE "-elf-gcc" "-elf-strip" ${CMAKE_STRIP} ${CMAKE_C_COMPILER})
set(strip_flags --strip-unneeded
--remove-section=.comment
--remove-section=.got.loc
--remove-section=.dynamic)
if(CONFIG_IDF_TARGET_ARCH_XTENSA)
list(APPEND strip_flags --remove-section=.xt.lit
--remove-section=.xt.prop
--remove-section=.xtensa.info)
elseif(CONFIG_IDF_TARGET_ARCH_RISCV)
list(APPEND strip_flags --remove-section=.riscv.attributes)
endif()
# Link input list of libraries to ELF
list(PREPEND ELF_COMPONENTS "main")
if(ELF_COMPONENTS)
foreach(c ${ELF_COMPONENTS})
list(APPEND elf_libs "esp-idf/${c}/lib${c}.a")
if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
add_custom_command(OUTPUT elf_${c}_app
COMMAND +${CMAKE_MAKE_PROGRAM} "__idf_${c}/fast"
COMMENT "Build Component: ${c}"
)
list(APPEND elf_dependeces "elf_${c}_app")
else()
list(APPEND elf_dependeces "idf::${c}")
endif()
endforeach()
endif()
if (ELF_LIBS)
list(APPEND elf_libs "${ELF_LIBS}")
endif()
spaces2list(elf_libs)
add_custom_command(OUTPUT elf_app
COMMAND ${CMAKE_C_COMPILER} ${cflags} ${elf_libs} -o ${elf_app}
COMMAND ${CMAKE_STRIP} ${strip_flags} ${elf_app}
DEPENDS ${elf_dependeces}
COMMENT "Build ELF: ${elf_app}"
)
add_custom_target(elf ALL DEPENDS elf_app)
endmacro()

View File

@ -1,19 +0,0 @@
version: "1.1.1"
targets:
- esp32
- esp32s2
- esp32s3
- esp32c6
- esp32c61
- esp32p4
description: Espressif ELF(Executable and Linkable Format) Loader
url: https://github.com/espressif/esp-iot-solution/tree/master/components/elf_loader
dependencies:
idf: ">=4.4.3"
espressif/cmake_utilities: "0.*"
examples:
- path: ../../examples/elf_loader/build_elf_file_example
- path: ../../examples/elf_loader/elf_loader_example
sbom:
supplier: 'Organization: Espressif Systems (Shanghai) CO LTD'
originator: 'Organization: Espressif Systems (Shanghai) CO LTD'

View File

@ -1,103 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "private/elf_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Map symbol's address of ELF to physic space.
*
* @param elf - ELF object pointer
* @param sym - ELF symbol address
*
* @return Mapped physic address.
*/
uintptr_t esp_elf_map_sym(esp_elf_t *elf, uintptr_t sym);
/**
* @brief Initialize ELF object.
*
* @param elf - ELF object pointer
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_init(esp_elf_t *elf);
/**
* @brief Decode and relocate ELF data.
*
* @param elf - ELF object pointer
* @param pbuf - ELF data buffer
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_relocate(esp_elf_t *elf, const uint8_t *pbuf);
/**
* @brief Request running relocated ELF function.
*
* @param elf - ELF object pointer
* @param opt - Request options
* @param argc - Arguments number
* @param argv - Arguments value array
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_request(esp_elf_t *elf, int opt, int argc, char *argv[]);
/**
* @brief Deinitialize ELF object.
*
* @param elf - ELF object pointer
*
* @return None
*/
void esp_elf_deinit(esp_elf_t *elf);
/**
* @brief Print header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_ehdr(const uint8_t *pbuf);
/**
* @brief Print program header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_phdr(const uint8_t *pbuf);
/**
* @brief Print section header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_shdr(const uint8_t *pbuf);
/**
* @brief Print section information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_sec(esp_elf_t *elf);
#ifdef __cplusplus
}
#endif

View File

@ -1,99 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "private/elf_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Notes: align_size needs to be a power of 2 */
#define ELF_ALIGN(_a, align_size) (((_a) + (align_size - 1)) & \
~(align_size - 1))
/**
* @brief Allocate block of memory.
*
* @param n - Memory size in byte
* @param exec - True: memory can run executable code; False: memory can R/W data
*
* @return Memory pointer if success or NULL if failed.
*/
void *esp_elf_malloc(uint32_t n, bool exec);
/**
* @brief Free block of memory.
*
* @param ptr - memory block pointer allocated by "esp_elf_malloc"
*
* @return None
*/
void esp_elf_free(void *ptr);
/**
* @brief Relocates target architecture symbol of ELF
*
* @param elf - ELF object pointer
* @param rela - Relocated symbol data
* @param sym - ELF symbol table
* @param addr - Jumping target address
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_arch_relocate(esp_elf_t *elf, const elf32_rela_t *rela,
const elf32_sym_t *sym, uint32_t addr);
/**
* @brief Remap symbol from ".data" to ".text" section.
*
* @param elf - ELF object pointer
* @param sym - ELF symbol table
*
* @return Remapped symbol value
*/
#ifdef CONFIG_ELF_LOADER_CACHE_OFFSET
uintptr_t elf_remap_text(esp_elf_t *elf, uintptr_t sym);
#endif
/**
* @brief Flush data from cache to external RAM.
*
* @param None
*
* @return None
*/
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
void esp_elf_arch_flush(void);
#endif
/**
* @brief Initialize MMU hardware remapping function.
*
* @param elf - ELF object pointer
*
* @return 0 if success or a negative value if failed.
*/
#ifdef CONFIG_ELF_LOADER_SET_MMU
int esp_elf_arch_init_mmu(esp_elf_t *elf);
#endif
/**
* @brief De-initialize MMU hardware remapping function.
*
* @param elf - ELF object pointer
*
* @return None
*/
#ifdef CONFIG_ELF_LOADER_SET_MMU
void esp_elf_arch_deinit_mmu(esp_elf_t *elf);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -1,54 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_ELFSYM_EXPORT(_sym) { #_sym, (void*)&_sym }
#define ESP_ELFSYM_END { NULL, NULL }
/** @brief Function symbol description */
struct esp_elfsym {
const char *name; /*!< Function name */
const void *sym; /*!< Function pointer */
};
/**
* @brief Find symbol address by name.
*
* @param sym_name - Symbol name
*
* @return Symbol address if success or 0 if failed.
*/
uintptr_t elf_find_sym(const char *sym_name);
/**
* @brief Resolves a symbol name (e.g. function name) to its address.
*
* @param sym_name - Symbol name
* @return Symbol address if success or 0 if failed.
*/
typedef uintptr_t (*symbol_resolver)(const char *sym_name);
/**
* @brief Override the internal symbol resolver.
* The default resolver is based on static lists that are determined by KConfig.
* This override allows for an arbitrary implementation.
*
* @param resolver the resolver function
*/
void elf_set_symbol_resolver(symbol_resolver resolver);
#ifdef __cplusplus
}
#endif

View File

@ -1,247 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <unistd.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#define EI_NIDENT 16 /*!< Magic number and other information length */
/** @brief Type of segment */
#define PT_NULL 0 /*!< Program header table entry unused */
#define PT_LOAD 1 /*!< Loadable program segment */
#define PT_DYNAMIC 2 /*!< Dynamic linking information */
#define PT_INTERP 3 /*!< Program interpreter */
#define PT_NOTE 4 /*!< Auxiliary information */
#define PT_SHLIB 5 /*!< Reserved */
#define PT_PHDR 6 /*!< Entry for header table itself */
#define PT_TLS 7 /*!< Thread-local storage segment */
#define PT_NUM 8 /*!< Number of defined types */
#define PT_LOOS 0x60000000 /*!< Start of OS-specific */
#define PT_GNU_EH_FRAME 0x6474e550 /*!< GCC .eh_frame_hdr segment */
#define PT_GNU_STACK 0x6474e551 /*!< Indicates stack executability */
#define PT_GNU_RELRO 0x6474e552 /*!< Read-only after relocation */
#define PT_LOSUNW 0x6ffffffa
#define PT_SUNWBSS 0x6ffffffa /*!< Sun Specific segment */
#define PT_SUNWSTACK 0x6ffffffb /*!< Stack segment */
#define PT_HISUNW 0x6fffffff
#define PT_HIOS 0x6fffffff /*!< End of OS-specific */
#define PT_LOPROC 0x70000000 /*!< Start of processor-specific */
#define PT_HIPROC 0x7fffffff /*!< End of processor-specific */
/** @brief Section Type */
#define SHT_NULL 0 /*!< invalid section header */
#define SHT_PROGBITS 1 /*!< some useful section like .text, .data .got, .plt .rodata .interp and so on */
#define SHT_SYMTAB 2 /*!< symbol table */
#define SHT_STRTAB 3 /*!< string table */
#define SHT_RELA 4 /*!< relocation table */
#define SHT_HASH 5 /*!< HASH table */
#define SHT_SYNAMIC 6 /*!< dynamic symbol table */
#define SHT_NOTE 7 /*!< note information */
#define SHT_NOBITS 8 /*!< .bss */
#define SHT_REL 9 /*!< relocation table */
#define SHT_SHKIB 10 /*!< reserved but has unspecified semantics. */
#define SHT_SYNSYM 11 /*!< dynamic symbol */
#define SHT_LOPROC 0x70000000 /*!< reserved for processor-specific semantics */
#define SHT_LOUSER 0x7fffffff /*!< lower bound of the range of indexes reserved for application programs */
#define SHT_HIUSER 0xffffffff /*!< upper bound of the range of indexes reserved for application programs. */
/** @brief Section Attribute Flags */
#define SHF_WRITE 1 /*!< writable when task runs */
#define SHF_ALLOC 2 /*!< allocated when task runs */
#define SHF_EXECINSTR 4 /*!< machine code */
#define SHF_MASKPROG 0xf0000000 /*!< reserved for processor-specific semantics */
/** @brief Symbol Types */
#define STT_NOTYPE 0 /*!< symbol type is unspecified */
#define STT_OBJECT 1 /*!< data object */
#define STT_FUNC 2 /*!< code object */
#define STT_SECTION 3 /*!< symbol identifies an ELF section */
#define STT_FILE 4 /*!< symbol's name is file name */
#define STT_COMMON 5 /*!< common data object */
#define STT_TLS 6 /*!< thread-local data object */
#define STT_NUM 7 /*!< defined types in generic range */
#define STT_LOOS 10 /*!< Low OS specific range */
#define STT_HIOS 12 /*!< High OS specific range */
#define STT_LOPROC 13 /*!< processor specific range */
#define STT_HIPROC 15 /*!< processor specific link range */
/** @brief Section names */
#define ELF_BSS ".bss" /*!< uninitialized data */
#define ELF_DATA ".data" /*!< initialized data */
#define ELF_DEBUG ".debug" /*!< debug */
#define ELF_DYNAMIC ".dynamic" /*!< dynamic linking information */
#define ELF_DYNSTR ".dynstr" /*!< dynamic string table */
#define ELF_DYNSYM ".dynsym" /*!< dynamic symbol table */
#define ELF_FINI ".fini" /*!< termination code */
#define ELF_GOT ".got" /*!< global offset table */
#define ELF_HASH ".hash" /*!< symbol hash table */
#define ELF_INIT ".init" /*!< initialization code */
#define ELF_REL_DATA ".rel.data" /*!< relocation data */
#define ELF_REL_FINI ".rel.fini" /*!< relocation termination code */
#define ELF_REL_INIT ".rel.init" /*!< relocation initialization code */
#define ELF_REL_DYN ".rel.dyn" /*!< relocaltion dynamic link info */
#define ELF_REL_RODATA ".rel.rodata" /*!< relocation read-only data */
#define ELF_REL_TEXT ".rel.text" /*!< relocation code */
#define ELF_RODATA ".rodata" /*!< read-only data */
#define ELF_SHSTRTAB ".shstrtab" /*!< section header string table */
#define ELF_STRTAB ".strtab" /*!< string table */
#define ELF_SYMTAB ".symtab" /*!< symbol table */
#define ELF_TEXT ".text" /*!< code */
#define ELF_DATA_REL_RO ".data.rel.ro" /*!< dynamic read-only data */
#define ELF_PLT ".plt" /*!< procedure linkage table. */
#define ELF_GOT_PLT ".got.plt" /*!< a table where resolved addresses from external functions are stored */
/** @brief ELF section and symbol operation */
#define ELF_SEC_TEXT 0
#define ELF_SEC_BSS 1
#define ELF_SEC_DATA 2
#define ELF_SEC_RODATA 3
#define ELF_SEC_DRLRO 4
#define ELF_SECS 5
#define ELF_ST_BIND(_i) ((_i) >> 4)
#define ELF_ST_TYPE(_i) ((_i) & 0xf)
#define ELF_ST_INFO(_b, _t) (((_b)<<4) + ((_t) & 0xf))
#define ELF_R_SYM(_i) ((_i) >> 8)
#define ELF_R_TYPE(_i) ((unsigned char)(_i))
#define ELF_R_INFO(_s, _t) (((_s) << 8) + (unsigned char)(_t))
#define ELF_SEC_MAP(_elf, _sec, _addr) \
((_elf)->sec[(_sec)].addr - \
(_elf)->sec[(_sec)].v_addr + \
(_addr))
typedef unsigned int Elf32_Addr;
typedef unsigned int Elf32_Off;
typedef unsigned int Elf32_Word;
typedef unsigned short Elf32_Half;
typedef int Elf32_Sword;
/** @brief ELF Header */
typedef struct elf32_hdr {
unsigned char ident[EI_NIDENT]; /*!< ELF Identification */
Elf32_Half type; /*!< object file type */
Elf32_Half machine; /*!< machine */
Elf32_Word version; /*!< object file version */
Elf32_Addr entry; /*!< virtual entry point */
Elf32_Off phoff; /*!< program header table offset */
Elf32_Off shoff; /*!< section header table offset */
Elf32_Word flags; /*!< processor-specific flags */
Elf32_Half ehsize; /*!< ELF header size */
Elf32_Half phentsize; /*!< program header entry size */
Elf32_Half phnum; /*!< number of program header entries */
Elf32_Half shentsize; /*!< section header entry size */
Elf32_Half shnum; /*!< number of section header entries */
Elf32_Half shstrndx; /*!< section header table's "section header string table" entry offset */
} elf32_hdr_t;
/** @brief Program Header */
typedef struct elf32_phdr {
Elf32_Word type; /* segment type */
Elf32_Off offset; /* segment offset */
Elf32_Addr vaddr; /* virtual address of segment */
Elf32_Addr paddr; /* physical address - ignored? */
Elf32_Word filesz; /* number of bytes in file for seg. */
Elf32_Word memsz; /* number of bytes in mem. for seg. */
Elf32_Word flags; /* flags */
Elf32_Word align; /* memory alignment */
} elf32_phdr_t;
/** @brief Section Header */
typedef struct elf32_shdr {
Elf32_Word name; /*!< section index and it is offset in section .shstrtab */
Elf32_Word type; /*!< type */
Elf32_Word flags; /*!< flags */
Elf32_Addr addr; /*!< start address when map to task space */
Elf32_Off offset; /*!< offset address from file start address */
Elf32_Word size; /*!< size */
Elf32_Word link; /*!< link to another section */
Elf32_Word info; /*!< additional section information */
Elf32_Word addralign; /*!< address align */
Elf32_Word entsize; /*!< index table size */
} elf32_shdr_t;
/** @brief Symbol Table Entry */
typedef struct elf32_sym {
Elf32_Word name; /*!< name - index into string table */
Elf32_Addr value; /*!< symbol value */
Elf32_Word size; /*!< symbol size */
unsigned char info; /*!< type and binding */
unsigned char other; /*!< 0 - no defined meaning */
Elf32_Half shndx; /*!< section header index */
} elf32_sym_t;
/** @brief Relocation entry with implicit addend */
typedef struct elf32_rel {
Elf32_Addr offset; /*!< offset of relocation */
Elf32_Word info; /*!< symbol table index and type */
} elf32_rel_t;
/** @brief Relocation entry with explicit addend */
typedef struct elf32_rela {
Elf32_Addr offset; /*!< offset of relocation */
Elf32_Word info; /*!< symbol table index and type */
Elf32_Sword addend; /*!< Added information */
} elf32_rela_t;
/** @brief ELF section object */
typedef struct esp_elf_sec {
uintptr_t v_addr; /*!< symbol virtual address */
off_t offset; /*!< offset in ELF */
uintptr_t addr; /*!< section physic address in memory */
size_t size; /*!< section size */
} esp_elf_sec_t;
/** @brief ELF object */
typedef struct esp_elf {
unsigned char *psegment; /*!< segment buffer pointer */
uint32_t svaddr; /*!< start virtual address of segment */
unsigned char *ptext; /*!< instruction buffer pointer */
unsigned char *pdata; /*!< data buffer pointer */
esp_elf_sec_t sec[ELF_SECS]; /*!< ".bss", "data", "rodata", ".text" */
int (*entry)(int argc, char *argv[]); /*!< Entry pointer of ELF */
#ifdef CONFIG_ELF_LOADER_SET_MMU
uint32_t text_off; /* .text symbol offset */
uint32_t mmu_off; /* MMU unit offset */
uint32_t mmu_num; /* MMU unit total number */
#endif
} esp_elf_t;
#ifdef __cplusplus
}
#endif

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,14 +0,0 @@
[sections:elf_loader_got_plt]
entries:
.got.plt
.got
[scheme:elf_loader_default]
entries:
elf_loader_got_plt -> flash_rodata
[mapping:elf_loader]
archive: *
entries:
* (elf_loader_default);
elf_loader_got_plt -> flash_rodata KEEP() SURROUND(_esp_elf_loader_got_plt)

View File

@ -1 +0,0 @@
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_MODULE_PATH})

View File

@ -1,112 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <sys/errno.h>
#include "esp_elf.h"
#include "esp_log.h"
#include "private/elf_platform.h"
/** @brief RISC-V relocations defined by the ABIs */
#define R_RISCV_NONE 0
#define R_RISCV_32 1
#define R_RISCV_64 2
#define R_RISCV_RELATIVE 3
#define R_RISCV_COPY 4
#define R_RISCV_JUMP_SLOT 5
#define R_RISCV_TLS_DTPMOD32 6
#define R_RISCV_TLS_DTPMOD64 7
#define R_RISCV_TLS_DTPREL32 8
#define R_RISCV_TLS_DTPREL64 9
#define R_RISCV_TLS_TPREL32 10
#define R_RISCV_TLS_TPREL64 11
#define R_RISCV_TLS_DESC 12
#define R_RISCV_BRANCH 16
#define R_RISCV_JAL 17
#define R_RISCV_CALL 18
#define R_RISCV_CALL_PLT 19
#define R_RISCV_GOT_HI20 20
#define R_RISCV_TLS_GOT_HI20 21
#define R_RISCV_TLS_GD_HI20 22
#define R_RISCV_PCREL_HI20 23
#define R_RISCV_PCREL_LO12_I 24
#define R_RISCV_PCREL_LO12_S 25
#define R_RISCV_HI20 26
#define R_RISCV_LO12_I 27
#define R_RISCV_LO12_S 28
#define R_RISCV_TPREL_HI20 29
#define R_RISCV_TPREL_LO12_I 30
#define R_RISCV_TPREL_LO12_S 31
#define R_RISCV_TPREL_ADD 32
#define R_RISCV_ADD8 33
#define R_RISCV_ADD16 34
#define R_RISCV_ADD32 35
#define R_RISCV_ADD64 36
#define R_RISCV_SUB8 37
#define R_RISCV_SUB16 38
#define R_RISCV_SUB32 39
#define R_RISCV_SUB64 40
#define R_RISCV_GNU_VTINHERIT 41
#define R_RISCV_GNU_VTENTRY 42
#define R_RISCV_ALIGN 43
#define R_RISCV_RVC_BRANCH 44
#define R_RISCV_RVC_JUMP 45
#define R_RISCV_RVC_LUI 46
#define R_RISCV_RELAX 51
#define R_RISCV_SUB6 52
#define R_RISCV_SET6 53
#define R_RISCV_SET8 54
#define R_RISCV_SET16 55
#define R_RISCV_SET32 56
#define R_RISCV_32_PCREL 57
#define R_RISCV_IRELATIVE 58
#define R_RISCV_PLT32 59
static const char *TAG = "elf_arch";
/**
* @brief Relocates target architecture symbol of ELF
*
* @param elf - ELF object pointer
* @param rela - Relocated symbol data
* @param sym - ELF symbol table
* @param addr - Jumping target address
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_arch_relocate(esp_elf_t *elf, const elf32_rela_t *rela,
const elf32_sym_t *sym, uint32_t addr)
{
uint32_t *where;
assert(elf && rela);
where = (uint32_t *)((uint8_t *)elf->psegment + rela->offset + elf->svaddr);
ESP_LOGD(TAG, "type: %d, where=%p addr=0x%x offset=0x%x",
ELF_R_TYPE(rela->info), where, (int)elf->psegment, (int)rela->offset);
/* Do relocation based on relocation type */
switch (ELF_R_TYPE(rela->info)) {
case R_RISCV_NONE:
break;
case R_RISCV_32:
*where = addr + rela->addend;
break;
case R_RISCV_RELATIVE:
*where = (Elf32_Addr)((uint8_t *)elf->psegment - elf->svaddr + rela->addend);
break;
case R_RISCV_JUMP_SLOT:
*where = addr;
break;
default:
ESP_LOGE(TAG, "info=%d is not supported\n", ELF_R_TYPE(rela->info));
return -EINVAL;
}
return 0;
}

View File

@ -1,113 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <sys/errno.h>
#include "esp_elf.h"
#include "esp_log.h"
#include "private/elf_platform.h"
/** @brief Xtensa relocations defined by the ABIs */
#define R_XTENSA_NONE 0
#define R_XTENSA_32 1
#define R_XTENSA_RTLD 2
#define R_XTENSA_GLOB_DAT 3
#define R_XTENSA_JMP_SLOT 4
#define R_XTENSA_RELATIVE 5
#define R_XTENSA_PLT 6
#define R_XTENSA_OP0 8
#define R_XTENSA_OP1 9
#define R_XTENSA_OP2 10
#define R_XTENSA_ASM_EXPAND 11
#define R_XTENSA_ASM_SIMPLIFY 12
#define R_XTENSA_GNU_VTINHERIT 15
#define R_XTENSA_GNU_VTENTRY 16
#define R_XTENSA_DIFF8 17
#define R_XTENSA_DIFF16 18
#define R_XTENSA_DIFF32 19
#define R_XTENSA_SLOT0_OP 20
#define R_XTENSA_SLOT1_OP 21
#define R_XTENSA_SLOT2_OP 22
#define R_XTENSA_SLOT3_OP 23
#define R_XTENSA_SLOT4_OP 24
#define R_XTENSA_SLOT5_OP 25
#define R_XTENSA_SLOT6_OP 26
#define R_XTENSA_SLOT7_OP 27
#define R_XTENSA_SLOT8_OP 28
#define R_XTENSA_SLOT9_OP 29
#define R_XTENSA_SLOT10_OP 30
#define R_XTENSA_SLOT11_OP 31
#define R_XTENSA_SLOT12_OP 32
#define R_XTENSA_SLOT13_OP 33
#define R_XTENSA_SLOT14_OP 34
#define R_XTENSA_SLOT0_ALT 35
#define R_XTENSA_SLOT1_ALT 36
#define R_XTENSA_SLOT2_ALT 37
#define R_XTENSA_SLOT3_ALT 38
#define R_XTENSA_SLOT4_ALT 39
#define R_XTENSA_SLOT5_ALT 40
#define R_XTENSA_SLOT6_ALT 41
#define R_XTENSA_SLOT7_ALT 42
#define R_XTENSA_SLOT8_ALT 43
#define R_XTENSA_SLOT9_ALT 44
#define R_XTENSA_SLOT10_ALT 45
#define R_XTENSA_SLOT11_ALT 46
#define R_XTENSA_SLOT12_ALT 47
#define R_XTENSA_SLOT13_ALT 48
#define R_XTENSA_SLOT14_ALT 49
static const char *TAG = "elf_arch";
/**
* @brief Relocates target architecture symbol of ELF
*
* @param elf - ELF object pointer
* @param rela - Relocated symbol data
* @param sym - ELF symbol table
* @param addr - Jumping target address
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_arch_relocate(esp_elf_t *elf, const elf32_rela_t *rela,
const elf32_sym_t *sym, uint32_t addr)
{
uint32_t val;
uint32_t *where;
assert(elf && rela);
where = (uint32_t *)esp_elf_map_sym(elf, rela->offset);
ESP_LOGD(TAG, "type: %d, where=%p addr=0x%x offset=0x%x\n",
ELF_R_TYPE(rela->info), where, (int)addr, (int)rela->offset);
switch (ELF_R_TYPE(rela->info)) {
case R_XTENSA_RELATIVE:
val = esp_elf_map_sym(elf, *where);
#ifdef CONFIG_ELF_LOADER_CACHE_OFFSET
*where = elf_remap_text(elf, val);
#else
*where = val;
#endif
break;
case R_XTENSA_RTLD:
break;
case R_XTENSA_GLOB_DAT:
case R_XTENSA_JMP_SLOT:
#ifdef CONFIG_ELF_LOADER_CACHE_OFFSET
*where = elf_remap_text(elf, addr);
#else
*where = addr;
#endif
break;
default:
ESP_LOGE(TAG, "info=%d is not supported", ELF_R_TYPE(rela->info));
return -EINVAL;
}
return 0;
}

View File

@ -1,21 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stddef.h>
#include "private/elf_symbol.h"
/* Extern declarations from ELF symbol table */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"
#pragma GCC diagnostic pop
/* Available ELF symbols table: g_customer_elfsyms */
const struct esp_elfsym g_customer_elfsyms[] = {
ESP_ELFSYM_END
};

View File

@ -1,676 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/param.h>
#include "esp_log.h"
#include "soc/soc_caps.h"
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#include "hal/cache_ll.h"
#endif
#include "private/elf_symbol.h"
#include "private/elf_platform.h"
#include "esp_elf.h"
#define stype(_s, _t) ((_s)->type == (_t))
#define sflags(_s, _f) (((_s)->flags & (_f)) == (_f))
#define ADDR_OFFSET (0x400)
uintptr_t elf_find_sym_default(const char *sym_name);
static const char *TAG = "ELF";
static symbol_resolver current_resolver = elf_find_sym_default;
/**
* @brief Find symbol address by name.
*
* @param sym_name - Symbol name
*
* @return Symbol address if success or 0 if failed.
*/
uintptr_t elf_find_sym(const char *sym_name) {
return current_resolver(sym_name);
}
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
/**
* @brief Load ELF section.
*
* @param elf - ELF object pointer
* @param pbuf - ELF data buffer
*
* @return ESP_OK if success or other if failed.
*/
static int esp_elf_load_section(esp_elf_t *elf, const uint8_t *pbuf)
{
uint32_t entry;
uint32_t size;
const elf32_hdr_t *ehdr = (const elf32_hdr_t *)pbuf;
const elf32_shdr_t *shdr = (const elf32_shdr_t *)(pbuf + ehdr->shoff);
const char *shstrab = (const char *)pbuf + shdr[ehdr->shstrndx].offset;
/* Calculate ELF image size */
for (uint32_t i = 0; i < ehdr->shnum; i++) {
const char *name = shstrab + shdr[i].name;
if (stype(&shdr[i], SHT_PROGBITS) && sflags(&shdr[i], SHF_ALLOC)) {
if (sflags(&shdr[i], SHF_EXECINSTR) && !strcmp(ELF_TEXT, name)) {
ESP_LOGD(TAG, ".text sec addr=0x%08x size=0x%08x offset=0x%08x",
shdr[i].addr, shdr[i].size, shdr[i].offset);
elf->sec[ELF_SEC_TEXT].v_addr = shdr[i].addr;
elf->sec[ELF_SEC_TEXT].size = ELF_ALIGN(shdr[i].size, 4);
elf->sec[ELF_SEC_TEXT].offset = shdr[i].offset;
ESP_LOGD(TAG, ".text offset is 0x%lx size is 0x%x",
elf->sec[ELF_SEC_TEXT].offset,
elf->sec[ELF_SEC_TEXT].size);
} else if (sflags(&shdr[i], SHF_WRITE) && !strcmp(ELF_DATA, name)) {
ESP_LOGD(TAG, ".data sec addr=0x%08x size=0x%08x offset=0x%08x",
shdr[i].addr, shdr[i].size, shdr[i].offset);
elf->sec[ELF_SEC_DATA].v_addr = shdr[i].addr;
elf->sec[ELF_SEC_DATA].size = shdr[i].size;
elf->sec[ELF_SEC_DATA].offset = shdr[i].offset;
ESP_LOGD(TAG, ".data offset is 0x%lx size is 0x%x",
elf->sec[ELF_SEC_DATA].offset,
elf->sec[ELF_SEC_DATA].size);
} else if (!strcmp(ELF_RODATA, name)) {
ESP_LOGD(TAG, ".rodata sec addr=0x%08x size=0x%08x offset=0x%08x",
shdr[i].addr, shdr[i].size, shdr[i].offset);
elf->sec[ELF_SEC_RODATA].v_addr = shdr[i].addr;
elf->sec[ELF_SEC_RODATA].size = shdr[i].size;
elf->sec[ELF_SEC_RODATA].offset = shdr[i].offset;
ESP_LOGD(TAG, ".rodata offset is 0x%lx size is 0x%x",
elf->sec[ELF_SEC_RODATA].offset,
elf->sec[ELF_SEC_RODATA].size);
} else if (!strcmp(ELF_DATA_REL_RO, name)) {
ESP_LOGD(TAG, ".data.rel.ro sec addr=0x%08x size=0x%08x offset=0x%08x",
shdr[i].addr, shdr[i].size, shdr[i].offset);
elf->sec[ELF_SEC_DRLRO].v_addr = shdr[i].addr;
elf->sec[ELF_SEC_DRLRO].size = shdr[i].size;
elf->sec[ELF_SEC_DRLRO].offset = shdr[i].offset;
ESP_LOGD(TAG, ".data.rel.ro offset is 0x%lx size is 0x%x",
elf->sec[ELF_SEC_DRLRO].offset,
elf->sec[ELF_SEC_DRLRO].size);
}
} else if (stype(&shdr[i], SHT_NOBITS) &&
sflags(&shdr[i], SHF_ALLOC | SHF_WRITE) &&
!strcmp(ELF_BSS, name)) {
ESP_LOGD(TAG, ".bss sec addr=0x%08x size=0x%08x offset=0x%08x",
shdr[i].addr, shdr[i].size, shdr[i].offset);
elf->sec[ELF_SEC_BSS].v_addr = shdr[i].addr;
elf->sec[ELF_SEC_BSS].size = shdr[i].size;
elf->sec[ELF_SEC_BSS].offset = shdr[i].offset;
ESP_LOGD(TAG, ".bss offset is 0x%lx size is 0x%x",
elf->sec[ELF_SEC_BSS].offset,
elf->sec[ELF_SEC_BSS].size);
}
}
/* No .text on image */
if (!elf->sec[ELF_SEC_TEXT].size) {
return -EINVAL;
}
elf->ptext = esp_elf_malloc(elf->sec[ELF_SEC_TEXT].size, true);
if (!elf->ptext) {
return -ENOMEM;
}
size = elf->sec[ELF_SEC_DATA].size +
elf->sec[ELF_SEC_RODATA].size +
elf->sec[ELF_SEC_BSS].size +
elf->sec[ELF_SEC_DRLRO].size;
if (size) {
elf->pdata = esp_elf_malloc(size, false);
if (!elf->pdata) {
esp_elf_free(elf->ptext);
return -ENOMEM;
}
}
/* Dump ".text" from ELF to executable space memory */
elf->sec[ELF_SEC_TEXT].addr = (Elf32_Addr)elf->ptext;
memcpy(elf->ptext, pbuf + elf->sec[ELF_SEC_TEXT].offset,
elf->sec[ELF_SEC_TEXT].size);
#ifdef CONFIG_ELF_LOADER_SET_MMU
if (esp_elf_arch_init_mmu(elf)) {
esp_elf_free(elf->ptext);
esp_elf_free(elf->pdata);
return -EIO;
}
#endif
/**
* Dump ".data", ".rodata" and ".bss" from ELF to R/W space memory.
*
* Todo: Dump ".rodata" to rodata section by MMU/MPU.
*/
if (size) {
uint8_t *pdata = elf->pdata;
if (elf->sec[ELF_SEC_DATA].size) {
elf->sec[ELF_SEC_DATA].addr = (uint32_t)pdata;
memcpy(pdata, pbuf + elf->sec[ELF_SEC_DATA].offset,
elf->sec[ELF_SEC_DATA].size);
pdata += elf->sec[ELF_SEC_DATA].size;
}
if (elf->sec[ELF_SEC_RODATA].size) {
elf->sec[ELF_SEC_RODATA].addr = (uint32_t)pdata;
memcpy(pdata, pbuf + elf->sec[ELF_SEC_RODATA].offset,
elf->sec[ELF_SEC_RODATA].size);
pdata += elf->sec[ELF_SEC_RODATA].size;
}
if (elf->sec[ELF_SEC_DRLRO].size) {
elf->sec[ELF_SEC_DRLRO].addr = (uint32_t)pdata;
memcpy(pdata, pbuf + elf->sec[ELF_SEC_DRLRO].offset,
elf->sec[ELF_SEC_DRLRO].size);
pdata += elf->sec[ELF_SEC_DRLRO].size;
}
if (elf->sec[ELF_SEC_BSS].size) {
elf->sec[ELF_SEC_BSS].addr = (uint32_t)pdata;
memset(pdata, 0, elf->sec[ELF_SEC_BSS].size);
}
}
/* Set ELF entry */
entry = ehdr->entry + elf->sec[ELF_SEC_TEXT].addr -
elf->sec[ELF_SEC_TEXT].v_addr;
#ifdef CONFIG_ELF_LOADER_CACHE_OFFSET
elf->entry = (void *)elf_remap_text(elf, (uintptr_t)entry);
#else
elf->entry = (void *)entry;
#endif
return 0;
}
#else
/**
* @brief Load ELF segment.
*
* @param elf - ELF object pointer
* @param pbuf - ELF data buffer
*
* @return ESP_OK if success or other if failed.
*/
static int esp_elf_load_segment(esp_elf_t *elf, const uint8_t *pbuf)
{
uint32_t size;
bool first_segment = false;
Elf32_Addr vaddr_s = 0;
Elf32_Addr vaddr_e = 0;
const elf32_hdr_t *ehdr = (const elf32_hdr_t *)pbuf;
const elf32_phdr_t *phdr = (const elf32_phdr_t *)(pbuf + ehdr->phoff);
for (int i = 0; i < ehdr->phnum; i++) {
if (phdr[i].type != PT_LOAD) {
continue;
}
if (phdr[i].memsz < phdr[i].filesz) {
ESP_LOGE(TAG, "Invalid segment[%d], memsz: %d, filesz: %d",
i, phdr[i].memsz, phdr[i].filesz);
return -EINVAL;
}
if (first_segment == true) {
vaddr_s = phdr[i].vaddr;
vaddr_e = phdr[i].vaddr + phdr[i].memsz;
first_segment = true;
if (vaddr_e < vaddr_s) {
ESP_LOGE(TAG, "Invalid segment[%d], vaddr: 0x%x, memsz: %d",
i, phdr[i].vaddr, phdr[i].memsz);
return -EINVAL;
}
} else {
if (phdr[i].vaddr < vaddr_e) {
ESP_LOGE(TAG, "Invalid segment[%d], should not overlap, vaddr: 0x%x, vaddr_e: 0x%x\n",
i, phdr[i].vaddr, vaddr_e);
return -EINVAL;
}
if (phdr[i].vaddr > vaddr_e + ADDR_OFFSET) {
ESP_LOGI(TAG, "Too much padding before segment[%d], padding: %d",
i, phdr[i].vaddr - vaddr_e);
}
vaddr_e = phdr[i].vaddr + phdr[i].memsz;
if (vaddr_e < phdr[i].vaddr) {
ESP_LOGE(TAG, "Invalid segment[%d], address overflow, vaddr: 0x%x, vaddr_e: 0x%x\n",
i, phdr[i].vaddr, vaddr_e);
return -EINVAL;
}
}
ESP_LOGD(TAG, "LOAD segment[%d], vaddr: 0x%x, memsize: 0x%08x",
i, phdr[i].vaddr, phdr[i].memsz);
}
size = vaddr_e - vaddr_s;
if (size == 0) {
return -EINVAL;
}
elf->svaddr = vaddr_s;
elf->psegment = esp_elf_malloc(size, true);
if (!elf->psegment) {
return -ENOMEM;
}
memset(elf->psegment, 0, size);
/* Dump "PT_LOAD" from ELF to memory space */
for (int i = 0; i < ehdr->phnum; i++) {
if (phdr[i].type == PT_LOAD) {
memcpy(elf->psegment + phdr[i].vaddr - vaddr_s,
(uint8_t *)pbuf + phdr[i].offset, phdr[i].filesz);
ESP_LOGD(TAG, "Copy segment[%d], mem_addr: 0x%x, vaddr: 0x%x, size: 0x%08x",
i, (int)((uint8_t *)elf->psegment + phdr[i].vaddr - vaddr_s),
phdr[i].vaddr, phdr[i].filesz);
}
}
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
cache_ll_writeback_all(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA, CACHE_LL_ID_ALL);
#endif
elf->entry = (void *)((uint8_t *)elf->psegment + ehdr->entry - vaddr_s);
return 0;
}
#endif
/**
* @brief Override the internal symbol resolver.
* The default resolver is based on static lists that are determined by KConfig.
* This override allows for an arbitrary implementation.
*
* @param resolver the resolver function
*/
void elf_set_symbol_resolver(symbol_resolver resolver) {
current_resolver = resolver;
}
/**
* @brief Map symbol's address of ELF to physic space.
*
* @param elf - ELF object pointer
* @param sym - ELF symbol address
*
* @return ESP_OK if success or other if failed.
*/
uintptr_t esp_elf_map_sym(esp_elf_t *elf, uintptr_t sym)
{
for (int i = 0; i < ELF_SECS; i++) {
if ((sym >= elf->sec[i].v_addr) &&
(sym < (elf->sec[i].v_addr + elf->sec[i].size))) {
return sym - elf->sec[i].v_addr + elf->sec[i].addr;
}
}
return 0;
}
/**
* @brief Initialize ELF object.
*
* @param elf - ELF object pointer
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_init(esp_elf_t *elf)
{
ESP_LOGI(TAG, "ELF loader version: %d.%d.%d", ELF_LOADER_VER_MAJOR, ELF_LOADER_VER_MINOR, ELF_LOADER_VER_PATCH);
if (!elf) {
return -EINVAL;
}
memset(elf, 0, sizeof(esp_elf_t));
return 0;
}
/**
* @brief Decode and relocate ELF data.
*
* @param elf - ELF object pointer
* @param pbuf - ELF data buffer
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_relocate(esp_elf_t *elf, const uint8_t *pbuf)
{
int ret;
const elf32_hdr_t *ehdr;
const elf32_shdr_t *shdr;
const char *shstrab;
if (!elf || !pbuf) {
return -EINVAL;
}
ehdr = (const elf32_hdr_t *)pbuf;
shdr = (const elf32_shdr_t *)(pbuf + ehdr->shoff);
shstrab = (const char *)pbuf + shdr[ehdr->shstrndx].offset;
/* Load section or segment to memory space */
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
ret = esp_elf_load_section(elf, pbuf);
#else
ret = esp_elf_load_segment(elf, pbuf);
#endif
if (ret) {
ESP_LOGE(TAG, "Error to load elf file, ret=%d", ret);
return ret;
}
ESP_LOGI(TAG, "elf->entry=%p\n", elf->entry);
/* Relocation section data */
for (uint32_t i = 0; i < ehdr->shnum; i++) {
if (stype(&shdr[i], SHT_RELA)) {
uint32_t nr_reloc;
const elf32_rela_t *rela;
const elf32_sym_t *symtab;
const char *strtab;
nr_reloc = shdr[i].size / sizeof(elf32_rela_t);
rela = (const elf32_rela_t *)(pbuf + shdr[i].offset);
symtab = (const elf32_sym_t *)(pbuf + shdr[shdr[i].link].offset);
strtab = (const char *)(pbuf + shdr[shdr[shdr[i].link].link].offset);
ESP_LOGD(TAG, "Section %s has %d symbol tables", shstrab + shdr[i].name, (int)nr_reloc);
for (int i = 0; i < nr_reloc; i++) {
int type;
uintptr_t addr = 0;
elf32_rela_t rela_buf;
memcpy(&rela_buf, &rela[i], sizeof(elf32_rela_t));
const elf32_sym_t *sym = &symtab[ELF_R_SYM(rela_buf.info)];
type = ELF_R_TYPE(rela_buf.info);
if (type == STT_COMMON || type == STT_OBJECT || type == STT_SECTION) {
const char *comm_name = strtab + sym->name;
if (comm_name[0]) {
addr = elf_find_sym(comm_name);
if (!addr) {
ESP_LOGE(TAG, "Can't find common %s", strtab + sym->name);
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
esp_elf_free(elf->pdata);
esp_elf_free(elf->ptext);
#else
esp_elf_free(elf->psegment);
#endif
return -ENOSYS;
}
ESP_LOGD(TAG, "Find common %s addr=%x", comm_name, addr);
}
} else if (type == STT_FILE) {
const char *func_name = strtab + sym->name;
if (sym->value) {
addr = esp_elf_map_sym(elf, sym->value);
} else {
addr = elf_find_sym(func_name);
}
if (!addr) {
ESP_LOGE(TAG, "Can't find symbol %s", func_name);
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
esp_elf_free(elf->pdata);
esp_elf_free(elf->ptext);
#else
esp_elf_free(elf->psegment);
#endif
return -ENOSYS;
}
ESP_LOGD(TAG, "Find function %s addr=%x", func_name, addr);
}
esp_elf_arch_relocate(elf, &rela_buf, sym, addr);
}
}
}
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
esp_elf_arch_flush();
#endif
return 0;
}
/**
* @brief Request running relocated ELF function.
*
* @param elf - ELF object pointer
* @param opt - Request options
* @param argc - Arguments number
* @param argv - Arguments value array
*
* @return ESP_OK if success or other if failed.
*/
int esp_elf_request(esp_elf_t *elf, int opt, int argc, char *argv[])
{
if (!elf || !(elf->entry)) {
return -EINVAL;
}
elf->entry(argc, argv);
return 0;
}
/**
* @brief Deinitialize ELF object.
*
* @param elf - ELF object pointer
*
* @return None
*/
void esp_elf_deinit(esp_elf_t *elf)
{
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
if (elf->pdata) {
esp_elf_free(elf->pdata);
elf->pdata = NULL;
}
if (elf->ptext) {
esp_elf_free(elf->ptext);
elf->ptext = NULL;
}
#else
if (elf->psegment) {
esp_elf_free(elf->ptext);
elf->ptext = NULL;
}
#endif
#ifdef CONFIG_ELF_LOADER_SET_MMU
esp_elf_arch_deinit_mmu(elf);
#endif
}
/**
* @brief Print header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_ehdr(const uint8_t *pbuf)
{
const char *s_bits, *s_endian;
const elf32_hdr_t *hdr = (const elf32_hdr_t *)pbuf;
switch (hdr->ident[4]) {
case 1:
s_bits = "32-bit";
break;
case 2:
s_bits = "64-bit";
break;
default:
s_bits = "invalid bits";
break;
}
switch (hdr->ident[5]) {
case 1:
s_endian = "little-endian";
break;
case 2:
s_endian = "big-endian";
break;
default:
s_endian = "invalid endian";
break;
}
if (hdr->ident[0] == 0x7f) {
ESP_LOGI(TAG, "%-40s %c%c%c", "Class:", hdr->ident[1], hdr->ident[2], hdr->ident[3]);
}
ESP_LOGI(TAG, "%-40s %s, %s", "Format:", s_bits, s_endian);
ESP_LOGI(TAG, "%-40s %x", "Version(current):", hdr->ident[6]);
ESP_LOGI(TAG, "%-40s %d", "Type:", hdr->type);
ESP_LOGI(TAG, "%-40s %d", "Machine:", hdr->machine);
ESP_LOGI(TAG, "%-40s %x", "Version:", hdr->version);
ESP_LOGI(TAG, "%-40s %x", "Entry point address:", hdr->entry);
ESP_LOGI(TAG, "%-40s %x", "Start of program headers:", hdr->phoff);
ESP_LOGI(TAG, "%-40s %d", "Start of section headers:", hdr->shoff);
ESP_LOGI(TAG, "%-40s 0x%x", "Flags:", hdr->flags);
ESP_LOGI(TAG, "%-40s %d", "Size of this header(bytes):", hdr->ehsize);
ESP_LOGI(TAG, "%-40s %d", "Size of program headers(bytes):", hdr->phentsize);
ESP_LOGI(TAG, "%-40s %d", "Number of program headers:", hdr->phnum);
ESP_LOGI(TAG, "%-40s %d", "Size of section headers(bytes):", hdr->shentsize);
ESP_LOGI(TAG, "%-40s %d", "Number of section headers:", hdr->shnum);
ESP_LOGI(TAG, "%-40s %d", "Section header string table i:", hdr->shstrndx);
}
/**
* @brief Print program header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_phdr(const uint8_t *pbuf)
{
const elf32_hdr_t *ehdr = (const elf32_hdr_t *)pbuf;
const elf32_phdr_t *phdr = (const elf32_phdr_t *)((size_t)pbuf + ehdr->phoff);
for (int i = 0; i < ehdr->phnum; i++) {
ESP_LOGI(TAG, "%-40s %d", "type:", phdr->type);
ESP_LOGI(TAG, "%-40s 0x%x", "offset:", phdr->offset);
ESP_LOGI(TAG, "%-40s 0x%x", "vaddr", phdr->vaddr);
ESP_LOGI(TAG, "%-40s 0x%x", "paddr:", phdr->paddr);
ESP_LOGI(TAG, "%-40s %d", "filesz", phdr->filesz);
ESP_LOGI(TAG, "%-40s %d", "memsz", phdr->memsz);
ESP_LOGI(TAG, "%-40s %d", "flags", phdr->flags);
ESP_LOGI(TAG, "%-40s 0x%x", "align", phdr->align);
phdr = (const elf32_phdr_t *)((size_t)phdr + sizeof(elf32_phdr_t));
}
}
/**
* @brief Print section header description information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_shdr(const uint8_t *pbuf)
{
const elf32_hdr_t *ehdr = (const elf32_hdr_t *)pbuf;
const elf32_shdr_t *shdr = (const elf32_shdr_t *)((size_t)pbuf + ehdr->shoff);
for (int i = 0; i < ehdr->shnum; i++) {
ESP_LOGI(TAG, "%-40s %d", "name:", shdr->name);
ESP_LOGI(TAG, "%-40s %d", "type:", shdr->type);
ESP_LOGI(TAG, "%-40s 0x%x", "flags:", shdr->flags);
ESP_LOGI(TAG, "%-40s %x", "addr", shdr->addr);
ESP_LOGI(TAG, "%-40s %x", "offset:", shdr->offset);
ESP_LOGI(TAG, "%-40s %d", "size", shdr->size);
ESP_LOGI(TAG, "%-40s 0x%x", "link", shdr->link);
ESP_LOGI(TAG, "%-40s %d", "addralign", shdr->addralign);
ESP_LOGI(TAG, "%-40s %d", "entsize", shdr->entsize);
shdr = (const elf32_shdr_t *)((size_t)shdr + sizeof(elf32_shdr_t));
}
}
/**
* @brief Print section information of ELF.
*
* @param pbuf - ELF data buffer
*
* @return None
*/
void esp_elf_print_sec(esp_elf_t *elf)
{
const char *sec_names[ELF_SECS] = {
"text", "bss", "data", "rodata"
};
for (int i = 0; i < ELF_SECS; i++) {
ESP_LOGI(TAG, "%s: 0x%08x size 0x%08x", sec_names[i], elf->sec[i].addr, elf->sec[i].size);
}
ESP_LOGI(TAG, "entry: %p", elf->entry);
}

View File

@ -1,120 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <sys/errno.h>
#include "esp_idf_version.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "soc/soc.h"
#include "private/elf_platform.h"
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
#ifdef CONFIG_IDF_TARGET_ESP32S3
#define OFFSET_TEXT_VALUE (SOC_IROM_LOW - SOC_DROM_LOW)
#endif
#endif
/**
* @brief Allocate block of memory.
*
* @param n - Memory size in byte
* @param exec - True: memory can run executable code; False: memory can R/W data
*
* @return Memory pointer if success or NULL if failed.
*/
void *esp_elf_malloc(uint32_t n, bool exec)
{
uint32_t caps;
#if CONFIG_ELF_LOADER_BUS_ADDRESS_MIRROR
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
#else
caps = exec ? MALLOC_CAP_EXEC : MALLOC_CAP_8BIT;
#endif
#else
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT;
#else
caps = MALLOC_CAP_8BIT;
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
caps |= MALLOC_CAP_CACHE_ALIGNED;
#endif
#endif
return heap_caps_malloc(n, caps);
}
/**
* @brief Free block of memory.
*
* @param ptr - memory block pointer allocated by "esp_elf_malloc"
*
* @return None
*/
void esp_elf_free(void *ptr)
{
heap_caps_free(ptr);
}
/**
* @brief Remap symbol from ".data" to ".text" section.
*
* @param elf - ELF object pointer
* @param sym - ELF symbol table
*
* @return Remapped symbol value
*/
#ifdef CONFIG_ELF_LOADER_CACHE_OFFSET
uintptr_t elf_remap_text(esp_elf_t *elf, uintptr_t sym)
{
uintptr_t mapped_sym;
esp_elf_sec_t *sec = &elf->sec[ELF_SEC_TEXT];
if ((sym >= sec->addr) &&
(sym < (sec->addr + sec->size))) {
#ifdef CONFIG_ELF_LOADER_SET_MMU
mapped_sym = sym + elf->text_off;
#else
mapped_sym = sym + OFFSET_TEXT_VALUE;
#endif
} else {
mapped_sym = sym;
}
return mapped_sym;
}
#endif
/**
* @brief Flush data from cache to external RAM.
*
* @param None
*
* @return None
*/
#ifdef CONFIG_ELF_LOADER_LOAD_PSRAM
void IRAM_ATTR esp_elf_arch_flush(void)
{
extern void spi_flash_disable_interrupts_caches_and_other_cpu(void);
extern void spi_flash_enable_interrupts_caches_and_other_cpu(void);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
extern void Cache_WriteBack_All(void);
Cache_WriteBack_All();
#else
void esp_spiram_writeback_cache(void);
esp_spiram_writeback_cache();
#endif
spi_flash_disable_interrupts_caches_and_other_cpu();
spi_flash_enable_interrupts_caches_and_other_cpu();
}
#endif

View File

@ -1,205 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <reent.h>
#include <pthread.h>
#include <setjmp.h>
#include <getopt.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "rom/ets_sys.h"
#include "private/elf_symbol.h"
extern int __ltdf2(double a, double b);
extern unsigned int __fixunsdfsi(double a);
extern int __gtdf2(double a, double b);
extern double __floatunsidf(unsigned int i);
extern double __divdf3(double a, double b);
/** @brief Libc public functions symbols look-up table */
static const struct esp_elfsym g_esp_libc_elfsyms[] = {
/* string.h */
ESP_ELFSYM_EXPORT(strerror),
ESP_ELFSYM_EXPORT(memset),
ESP_ELFSYM_EXPORT(memcpy),
ESP_ELFSYM_EXPORT(strlen),
ESP_ELFSYM_EXPORT(strtod),
ESP_ELFSYM_EXPORT(strrchr),
ESP_ELFSYM_EXPORT(strchr),
ESP_ELFSYM_EXPORT(strcmp),
ESP_ELFSYM_EXPORT(strtol),
ESP_ELFSYM_EXPORT(strcspn),
ESP_ELFSYM_EXPORT(strncat),
/* stdio.h */
ESP_ELFSYM_EXPORT(puts),
ESP_ELFSYM_EXPORT(putchar),
ESP_ELFSYM_EXPORT(fputc),
ESP_ELFSYM_EXPORT(fputs),
ESP_ELFSYM_EXPORT(printf),
ESP_ELFSYM_EXPORT(vfprintf),
ESP_ELFSYM_EXPORT(fprintf),
ESP_ELFSYM_EXPORT(fwrite),
/* unistd.h */
ESP_ELFSYM_EXPORT(usleep),
ESP_ELFSYM_EXPORT(sleep),
ESP_ELFSYM_EXPORT(exit),
ESP_ELFSYM_EXPORT(close),
/* stdlib.h */
ESP_ELFSYM_EXPORT(malloc),
ESP_ELFSYM_EXPORT(calloc),
ESP_ELFSYM_EXPORT(realloc),
ESP_ELFSYM_EXPORT(free),
/* time.h */
ESP_ELFSYM_EXPORT(clock_gettime),
ESP_ELFSYM_EXPORT(strftime),
/* pthread.h */
ESP_ELFSYM_EXPORT(pthread_create),
ESP_ELFSYM_EXPORT(pthread_attr_init),
ESP_ELFSYM_EXPORT(pthread_attr_setstacksize),
ESP_ELFSYM_EXPORT(pthread_detach),
ESP_ELFSYM_EXPORT(pthread_join),
ESP_ELFSYM_EXPORT(pthread_exit),
/* newlib */
ESP_ELFSYM_EXPORT(__errno),
ESP_ELFSYM_EXPORT(__getreent),
#ifdef __HAVE_LOCALE_INFO__
ESP_ELFSYM_EXPORT(__locale_ctype_ptr),
#else
ESP_ELFSYM_EXPORT(_ctype_),
#endif
/* math */
ESP_ELFSYM_EXPORT(__ltdf2),
ESP_ELFSYM_EXPORT(__fixunsdfsi),
ESP_ELFSYM_EXPORT(__gtdf2),
ESP_ELFSYM_EXPORT(__floatunsidf),
ESP_ELFSYM_EXPORT(__divdf3),
/* getopt.h */
ESP_ELFSYM_EXPORT(getopt_long),
ESP_ELFSYM_EXPORT(optind),
ESP_ELFSYM_EXPORT(opterr),
ESP_ELFSYM_EXPORT(optarg),
ESP_ELFSYM_EXPORT(optopt),
/* setjmp.h */
ESP_ELFSYM_EXPORT(longjmp),
ESP_ELFSYM_EXPORT(setjmp),
ESP_ELFSYM_END
};
/** @brief ESP-IDF public functions symbols look-up table */
static const struct esp_elfsym g_esp_espidf_elfsyms[] = {
/* sys/socket.h */
ESP_ELFSYM_EXPORT(lwip_bind),
ESP_ELFSYM_EXPORT(lwip_setsockopt),
ESP_ELFSYM_EXPORT(lwip_socket),
ESP_ELFSYM_EXPORT(lwip_listen),
ESP_ELFSYM_EXPORT(lwip_accept),
ESP_ELFSYM_EXPORT(lwip_recv),
ESP_ELFSYM_EXPORT(lwip_recvfrom),
ESP_ELFSYM_EXPORT(lwip_send),
ESP_ELFSYM_EXPORT(lwip_sendto),
ESP_ELFSYM_EXPORT(lwip_connect),
/* arpa/inet.h */
ESP_ELFSYM_EXPORT(ipaddr_addr),
ESP_ELFSYM_EXPORT(lwip_htons),
ESP_ELFSYM_EXPORT(lwip_htonl),
ESP_ELFSYM_EXPORT(ip4addr_ntoa),
/* ROM functions */
ESP_ELFSYM_EXPORT(ets_printf),
ESP_ELFSYM_END
};
/**
* @brief Find symbol address by name.
*
* @param sym_name - Symbol name
*
* @return Symbol address if success or 0 if failed.
*/
uintptr_t elf_find_sym_default(const char *sym_name)
{
const struct esp_elfsym *syms;
#ifdef CONFIG_ELF_LOADER_LIBC_SYMBOLS
syms = g_esp_libc_elfsyms;
while (syms->name) {
if (!strcmp(syms->name, sym_name)) {
return (uintptr_t)syms->sym;
}
syms++;
}
#else
syms = g_esp_libc_elfsyms;
(void)syms;
#endif
#ifdef CONFIG_ELF_LOADER_ESPIDF_SYMBOLS
syms = g_esp_espidf_elfsyms;
while (syms->name) {
if (!strcmp(syms->name, sym_name)) {
return (uintptr_t)syms->sym;
}
syms++;
}
#else
syms = g_esp_espidf_elfsyms;
(void)syms;
#endif
#ifdef CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS
extern const struct esp_elfsym g_customer_elfsyms[];
syms = g_customer_elfsyms;
while (syms->name) {
if (!strcmp(syms->name, sym_name)) {
return (uintptr_t)syms->sym;
}
syms++;
}
#endif
return 0;
}

View File

@ -1,133 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <sys/errno.h>
#include "esp_idf_version.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp32s2/rom/cache.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp32s2/spiram.h"
#endif
#include "soc/mmu.h"
#include "private/elf_platform.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define PSRAM_VADDR_START 0x3f800000
#else
#define PSRAM_VADDR_START (DRAM0_CACHE_ADDRESS_HIGH - esp_spiram_get_size())
#endif
#define MMU_INVALID BIT(14)
#define MMU_UNIT_SIZE 0x10000
#define MMU_REG ((volatile uint32_t *)DR_REG_MMU_TABLE)
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
#define IRAM0_ADDRESS_LOW SOC_IRAM0_ADDRESS_LOW
#define IRAM0_ADDRESS_HIGH SOC_IRAM0_ADDRESS_HIGH
#define MMU_ACCESS_SPIRAM SOC_MMU_ACCESS_SPIRAM
#endif
#define ESP_MMU_IBUS_BASE IRAM0_ADDRESS_LOW
#define ESP_MMU_IBUS_MAX ((IRAM0_ADDRESS_HIGH - IRAM0_ADDRESS_LOW) / \
MMU_UNIT_SIZE)
#define ESP_MMU_IBUS_OFF (8)
#define DADDR_2_SECS(v) ((v) / MMU_UNIT_SIZE)
#define IADDR_2_SECS(v) ((v) / MMU_UNIT_SIZE)
#define PSRAM_OFF(v) ((v) - PSRAM_VADDR_START)
#define PSRAM_SECS(v) DADDR_2_SECS(PSRAM_OFF((uintptr_t)(v)))
#define PSRAM_ALIGN(v) ((uintptr_t)(v) & (~(MMU_UNIT_SIZE - 1)))
#define ICACHE_ADDR(s) (ESP_MMU_IBUS_BASE + (s) * MMU_UNIT_SIZE)
extern void spi_flash_disable_interrupts_caches_and_other_cpu(void);
extern void spi_flash_enable_interrupts_caches_and_other_cpu(void);
/**
* @brief Initialize MMU hardware remapping function.
*
* @param elf - ELF object pointer
*
* @return 0 if success or a negative value if failed.
*/
int IRAM_ATTR esp_elf_arch_init_mmu(esp_elf_t *elf)
{
int ret;
int off;
uint32_t ibus_secs;
volatile uint32_t dbus_secs;
esp_elf_sec_t *sec = &elf->sec[ELF_SEC_TEXT];
volatile uint32_t *p = MMU_REG;
if (sec->size % MMU_UNIT_SIZE) {
ibus_secs = IADDR_2_SECS(sec->size) + 1;
} else {
ibus_secs = IADDR_2_SECS(sec->size);
}
dbus_secs = PSRAM_SECS(elf->ptext);
spi_flash_disable_interrupts_caches_and_other_cpu();
for (off = ESP_MMU_IBUS_OFF; off < ESP_MMU_IBUS_MAX; off++) {
if (p[off] == MMU_INVALID) {
int j;
for (j = 1; j < ibus_secs; j++) {
if (p[off + j] != MMU_INVALID) {
break;
}
}
if (j >= ibus_secs) {
for (int k = 0; k < ibus_secs; k++) {
p [off + k] = MMU_ACCESS_SPIRAM | (dbus_secs + k);
}
break;
}
}
}
spi_flash_enable_interrupts_caches_and_other_cpu();
if (off >= ESP_MMU_IBUS_MAX) {
ret = -EIO;
} else {
elf->mmu_off = off;
elf->mmu_num = ibus_secs;
elf->text_off = ICACHE_ADDR(off) - PSRAM_ALIGN(elf->ptext);
ret = 0;
}
return ret;
}
/**
* @brief De-initialize MMU hardware remapping function.
*
* @param elf - ELF object pointer
*
* @return None
*/
void IRAM_ATTR esp_elf_arch_deinit_mmu(esp_elf_t *elf)
{
volatile int num = elf->mmu_num;
volatile int off = elf->mmu_off;
volatile uint32_t *p = MMU_REG;
spi_flash_disable_interrupts_caches_and_other_cpu();
for (int i = 0; i < num; i++) {
p [off + i] = MMU_INVALID;
}
spi_flash_enable_interrupts_caches_and_other_cpu();
}

View File

@ -1,268 +0,0 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import argparse
import sys
import re
import subprocess
def get_global_symbols(lines, type, undefined=False, symbol_types=None):
"""
Extract global symbols from the given lines of a symbol table.
:param lines: List of lines from the symbol table, each representing a symbol.
:param type: Type of the input file ('e' for ELF files, 'l' for static libraries).
:param undefined: If True, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols.
:param symbol_types: A list of symbol types to filter by (e.g., ['FUNC', 'OBJECT']). If None, no filtering by type.
:return: List of symbol names if any match the criteria; otherwise, returns an empty list.
"""
symbols = []
if type == 'e':
# Pattern for ELF files
if not undefined:
pattern = re.compile(
r'^\s*\d+:\s+(\S+)\s+(\d+)\s+(FUNC|OBJECT)\s+GLOBAL\s+DEFAULT\s+(?:\d+|ABS|UND|COM|DEBUG)\s+(\S+)',
re.MULTILINE
)
else:
pattern = re.compile(
r'^\s*\d*:\s*\w*\s*\d*\s*NOTYPE\s*GLOBAL\s*DEFAULT\s*UND\s+(\S*)',
re.MULTILINE
)
for line in lines:
match = pattern.match(line)
if match:
if not undefined:
address, size, symbol_type, symbol_name = match.groups()
# Filter by symbol type if specified
if symbol_types and symbol_type not in symbol_types:
continue
symbols.append(symbol_name)
else:
symbol_name = match.group(1)
symbols.append(symbol_name)
elif type == 'l':
# Patterns for static libraries
func_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[TD]\s+(\S+)$')
var_pattern = re.compile(r'^\s*[0-9a-fA-F]+\s+[BD]\s+(\S+)$')
for line in lines:
if not undefined:
func_match = func_pattern.match(line)
var_match = var_pattern.match(line)
if func_match:
symbols.append(func_match.group(1))
elif var_match:
symbols.append(var_match.group(1))
return symbols
def save_c_file(symbols, output, symbol_table, exclude_symbols=None):
"""
Write extern declarations and ESP_ELFSYM structure to a C file, excluding specified symbols.
:param symbols: List of symbol names.
:param output: Path to the output C file.
:param exclude_symbols: List of symbol names to exclude; defaults to ['elf_find_sym'].
"""
if exclude_symbols is None:
exclude_symbols = ['elf_find_sym'] # Set default excluded symbols
# Filter out excluded symbols
filtered_symbols = [name for name in symbols if name not in exclude_symbols]
# Build the content of the C file
buf = '/*\n'
buf += ' * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD\n'
buf += ' *\n'
buf += ' * SPDX-License-Identifier: Apache-2.0\n'
buf += ' */\n\n'
# Add standard library headers
libc_headers = ['stddef'] # Add more headers as needed
buf += '\n'.join([f'#include <{h}.h>' for h in libc_headers]) + '\n\n'
# Add custom header
buf += '#include "private/elf_symbol.h"\n\n'
# Generate extern declarations if there are symbols
if filtered_symbols:
buf += '/* Extern declarations from ELF symbol table */\n\n'
buf += '#pragma GCC diagnostic push\n'
buf += '#pragma GCC diagnostic ignored "-Wbuiltin-declaration-mismatch"\n'
for symbol_name in filtered_symbols:
buf += f'extern int {symbol_name};\n'
buf += '#pragma GCC diagnostic pop\n\n'
# Define the symbol table structure with dynamic variable name
symbol_table_var = f'g_{symbol_table}_elfsyms'
buf += f'/* Available ELF symbols table: {symbol_table_var} */\n'
buf += f'\nconst struct esp_elfsym {symbol_table_var}[] = {{\n'
# Generate ESP_ELFSYM_EXPORT entries
if filtered_symbols:
for symbol_name in filtered_symbols:
buf += f' ESP_ELFSYM_EXPORT({symbol_name}),\n'
# End the symbol table
buf += ' ESP_ELFSYM_END\n'
buf += '};\n'
# Write to the file
with open(output, 'w+') as f:
f.write(buf)
def main():
"""
Main function to parse command-line arguments and process the input file's symbol table.
"""
parser = argparse.ArgumentParser(description='Extract all public functions from an application project', prog='symbols')
parser.add_argument(
'--output-file', '-of',
help='Custom output file path with filename (overrides --output)',
default=None
)
parser.add_argument(
'--input', '-i',
help='Input file name with full path',
required=True
)
parser.add_argument(
'--undefined', '-u',
action='store_true',
help='If set, only extract undefined (UND) symbols; otherwise, extract all GLOBAL symbols.',
default=False
)
parser.add_argument(
'--exclude', '-e',
nargs='+',
help='Symbols to exclude from the generated C file (e.g., memcpy __ltdf2). Default: elf_find_sym',
default=[] # User can extend this list
)
parser.add_argument(
'--type', '-t',
choices=['e', 'l'],
required=True,
help='Type of the input file: "elf" for ELF file, "lib" for static library (.a)'
)
parser.add_argument(
'--debug', '-d',
help='Debug level(option is \'debug\')',
default='no',
type=str)
args = parser.parse_args()
# Configure logging
if args.debug == 'debug':
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
else:
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Get the absolute path of the current file
cur_dir = os.path.dirname(os.path.abspath(__file__))
if args.type == 'e':
extracted_part = 'customer'
elif args.type == 'l':
# Convert relative path to absolute path
input_abs_path = os.path.abspath(args.input)
# Get the base name of the input file (without extension)
input_basename = os.path.basename(input_abs_path)
# Use a regular expression to extract the middle part
match = re.search(r'^lib(.+)\.a$', input_basename)
if match:
extracted_part = match.group(1)
else:
logging.error('Invalid input file name format. Expected format: lib<name>.a')
sys.exit(1)
# Determine the output file path
if args.output_file:
# Use the custom file path provided by the user
elfsym_file_dir = os.path.abspath(args.output_file)
output_dir = os.path.dirname(elfsym_file_dir)
output_abs_path = os.path.abspath(args.output_file)
output_basename = os.path.basename(output_abs_path)
extracted_part = os.path.splitext(output_basename)[0]
else:
# Use the default behavior: generate the file in the parent directory's 'src' folder
parent_dir = os.path.dirname(cur_dir)
output_dir = os.path.join(parent_dir, 'src') # Default directory is 'src' under the parent directory
output_file_name = f'esp_all_symbol.c'
elfsym_file_dir = os.path.join(output_dir, output_file_name)
# Ensure the output directory exists
os.makedirs(output_dir, exist_ok=True)
# Set default excluded symbols and allow user to extend the list
exclude_symbols = ['elf_find_sym', 'g_customer_elfsyms'] + args.exclude
if args.type == 'e':
cmd = ['readelf', '-s', '-W', args.input]
elif args.type == 'l':
cmd = ['nm', '--defined-only', '-g', args.input]
# Execute the readelf or nm command for static libraries
try:
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
lines = result.stdout.splitlines()
except subprocess.CalledProcessError as e:
logging.error(f'Error executing command: {e.stderr}')
sys.exit(1)
except FileNotFoundError:
logging.error('nm command not found. Please ensure it is installed and available in your PATH.')
sys.exit(1)
# Extract global symbols from ELF file or static library
symbols = get_global_symbols(lines, type=args.type, undefined=args.undefined, symbol_types=['FUNC', 'OBJECT'])
if not symbols:
logging.warning('No global symbols found in the input file.')
sys.exit(0)
logging.debug('symbols: %s'%(cmd))
logging.debug('symbols: %s'%(symbols))
logging.debug('elfsym_file_dir: %s'%(elfsym_file_dir))
logging.debug('extracted_part: %s'%(extracted_part))
logging.debug('exclude_symbols: %s'%(exclude_symbols))
# Save the C file
try:
save_c_file(symbols, elfsym_file_dir, extracted_part, exclude_symbols=exclude_symbols)
logging.info(f"C file with extern declarations and symbol table has been saved to '{elfsym_file_dir}'.")
except Exception as e:
logging.error(f'Error writing C file: {e}')
sys.exit(1)
def _main():
"""
Wrapper for the main function to catch and handle runtime errors.
"""
try:
main()
except RuntimeError as e:
logging.error(f'A fatal error occurred: {e}')
sys.exit(2)
if __name__ == '__main__':
_main()

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Which readiness condition to block for in AppFileOps::await(). */
typedef enum {
APP_FILE_WAIT_READABLE,
APP_FILE_WAIT_WRITABLE,
} AppFileWait;
/** Bits returned by AppFileOps::poll(). */
#define APP_FILE_READABLE (1u << 0)
#define APP_FILE_WRITABLE (1u << 1)
/**
* Operations table for one kind of file-like object (stream, file, device, ...). Every function
* takes the type-erased `object` a concrete AppFile instance was constructed with.
*/
struct AppFileOps {
ssize_t (*read)(void* object, void* buffer, size_t size);
ssize_t (*write)(void* object, const void* buffer, size_t size);
error_t (*close)(void* object);
error_t (*await)(void* object, AppFileWait wait, TickType_t timeout);
/** @return bitmask of APP_FILE_READABLE / APP_FILE_WRITABLE. */
uint32_t (*poll)(void* object);
/** Optional (may be NULL). Called by app_fd_table_get_and_retain() atomically with the fd
* lookup, before handing `object` to a caller about to dispatch into it, so a concurrent
* teardown of `object` can wait for that dispatch to finish instead of racing it. The caller
* calls release() once done, regardless of what the dispatched call returned. */
void (*retain)(void* object);
void (*release)(void* object);
};
/** A file-descriptor-table entry: an operations table paired with the object it operates on. */
struct AppFile {
const struct AppFileOps* ops;
void* object;
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <stddef.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
/** Fixed size of an app instance's fd table. FD allocation uses the lowest unused index >= 3. */
#define APP_MAX_FDS 16
/**
* FD-table dispatch for read()/write()/close(). When the calling task belongs to a running app
* instance and @a fd is one that instance bound/allocated itself, it's looked up in that
* instance's own fd table. Otherwise (no app instance, e.g. a kernel service task; or an app
* instance's own fd that was never bound through app-module, e.g. a real file from fopen()/
* open(), which this layer never intercepts) @a fd is a real underlying fd and the call falls
* through to the real syscall unchanged.
* @return number of bytes transferred, 0 on EOF (read) or a closed peer (write), or -1 with
* errno set on failure. This is the fd-layer translation of AppFileOps::read()/write()'s ssize_t
* and AppFileOps::close()'s error_t.
*/
ssize_t app_io_read(int fd, void* buffer, size_t size);
ssize_t app_io_write(int fd, const void* buffer, size_t size);
int app_io_close(int fd);
#ifdef __cplusplus
}
#endif

View File

@ -3,6 +3,7 @@
#include <app/instance.h>
#include <app/manifest.h>
#include <app/stream.h>
#include <tactility/error.h>
@ -86,6 +87,30 @@ error_t app_manager_start_with_parameters(const char* id, int argc, const char*
*/
error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id);
/** One fd-to-stream binding for app_manager_start_with_streams(). Every field is passed through
* to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */
struct AppStreamBinding {
int producer_fd;
struct AppStream* stream;
void* buffer;
size_t buffer_capacity;
struct TaskEventGroup* event_group;
};
/**
* Same as app_manager_start(), but installs @a bindings into the new instance's fd table before
* its task begins executing (e.g. a child's stdio, piped through parent-owned AppStreams; see
* app/stream.h). Writes the new instance's id into each bound stream's producer_id itself, since
* the caller cannot know it in advance.
* @param[in] bindings @a binding_count entries; each stream and buffer must stay alive (see
* app_stream_subscribe()) until unsubscribed or the child exits.
* @retval ERROR_NOT_FOUND no manifest with this id is registered, or no AppLoaderApi is registered
* @retval ERROR_OUT_OF_RANGE a binding's producer_fd is out of range
* @retval ERROR_RESOURCE a binding's event_group has no free bits left to claim
* @retval ERROR_NONE on success
*/
error_t app_manager_start_with_streams(const char* id, const struct AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id);
/**
* Stop an app instance permanently. Emits APP_EVENT_CLOSE and bound-waits for its task to exit
* if it was running.

View File

@ -0,0 +1,115 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/file.h>
#include <app/instance.h>
#include <stddef.h>
#include <stdint.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/concurrent/task_event_group.h>
#include <tactility/error.h>
#include <tactility/freertos/freertos.h>
#include <tactility/freertos/task.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @warning This is internal data. Do not read/write to it directly. */
struct AppStreamBuffer {
uint8_t* data;
size_t capacity;
size_t read_pos;
size_t write_pos;
size_t count;
};
/**
* Buffered byte-oriented communication between one producer task and one consumer task. The
* consumer owns the AppStream object and its lifetime. See app_stream_subscribe().
*
* @warning This is internal data. Do not read/write to it directly.
*/
struct AppStream {
struct AppStreamBuffer buffer;
AppInstanceId producer_id;
TaskHandle_t producer_task;
/** fd this stream is installed at in producer_id's fd table; set by
* app_stream_subscribe()/app_manager_start_with_streams(), used by
* app_stream_unsubscribe() to find it again. */
int producer_fd;
struct Mutex mutex;
/** Caller-owned group readiness is signalled on; see app_stream_subscribe(). */
struct TaskEventGroup* event_group;
uint32_t readable_bit;
uint32_t writable_bit;
bool closed;
/** Count of AppFileOps calls currently executing against this stream; app_stream_unsubscribe()
* waits for this to reach 0 before destructing `mutex`, since a call already blocked in
* app_stream_await() when unsubscribe starts only wakes (it doesn't vanish) when closed. */
int active_operations;
};
/**
* Registers @a stream as the file-like object bound to @a producer_id's fd table at
* @a producer_fd, claiming two bits from @a event_group for its readable/writable readiness.
* There is at most one subscriber for a given app fd. Subscribing over an existing one
* atomically closes it first (any task blocked on it wakes; already-buffered bytes remain
* readable until drained) and installs @a stream in its place.
* @param[in,out] stream caller-owned. Storage must remain valid until app_stream_unsubscribe()
* returns. That is the only operation guaranteeing both that the fd-table binding is gone and
* that no AppFileOps call is still executing against @a stream; app_stream_close() alone does
* neither.
* @param[in] buffer ring buffer storage; caller-owned, same validity requirement as @a stream.
* @param[in] buffer_capacity size of @a buffer in bytes.
* @param[in] event_group caller-owned; must outlive @a stream (i.e. be destructed only after
* app_stream_unsubscribe()). Lets a task wait on this stream's readiness together with other
* event sources sharing the same group via task_event_group_wait()/task_event_group_wait_any().
* @retval ERROR_NOT_FOUND no instance with this id is running
* @retval ERROR_OUT_OF_RANGE @a producer_fd is out of range
* @retval ERROR_RESOURCE @a event_group has no free bits left to claim
* @retval ERROR_NONE on success
*/
error_t app_stream_subscribe(struct AppStream* stream, void* buffer, size_t buffer_capacity, struct TaskEventGroup* event_group, AppInstanceId producer_id, int producer_fd);
/**
* Removes @a stream's binding from whichever fd table it was installed in (further use of that
* fd then fails), wakes and waits for every AppFileOps call currently in flight against
* @a stream to finish, then releases its two bits back to the event_group given to
* app_stream_subscribe() and destructs its internal mutex. Only after this returns is
* @a stream's storage safe to free or reuse.
*/
error_t app_stream_unsubscribe(struct AppStream* stream);
/**
* Blocks until @a stream becomes readable/writable (per @a wait) or @a timeout elapses. Checks
* the current state directly before blocking, so a permanently-true condition (e.g. closed)
* keeps returning immediately on every call, even though the underlying event bit itself is
* one-shot (task_event_group_wait() clears a matched bit on exit).
* @retval ERROR_NONE the condition is true
* @retval ERROR_TIMEOUT @a timeout elapsed
* @retval ERROR_ISR_STATUS called from an ISR
*/
error_t app_stream_await(struct AppStream* stream, AppFileWait wait, TickType_t timeout);
/** Non-blocking: copies up to @a buffer_size currently-available bytes out of @a stream. */
size_t app_stream_read(struct AppStream* stream, void* buffer, size_t buffer_size);
/** Non-blocking: copies up to @a buffer_size bytes into @a stream, as much as currently fits.
* Copies nothing and returns 0 once @a stream is closed. */
size_t app_stream_write(struct AppStream* stream, const void* buffer, size_t buffer_size);
/**
* Marks @a stream closed: wakes any task blocked in app_stream_await(), and causes the other side
* to observe EOF (reader, once drained) or a write error (writer). Safe to call while another
* task may be blocked on @a stream, unlike app_stream_unsubscribe() (see its own doc); does not
* by itself make @a stream's storage safe to free.
*/
error_t app_stream_close(struct AppStream* stream);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,98 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/file.h>
#include <app/io.h>
#include <stdbool.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/error.h>
/**
* One fd's worth of state in an AppFdTable. `file` is valid only while `in_use` is true.
* `ever_used` is set once `in_use` is ever set true (including the default stdio binding at
* construct time) and never cleared, even once `in_use` goes back to false on close. This is
* what lets app_fd_table_is_app_owned() distinguish "this fd number belongs to us, it's just
* currently closed" (an app-level EBADF) from "app-module has never touched this fd number" (a
* real underlying fd, e.g. from fopen()/open(), that a caller should fall through on).
*
* @warning This is internal data. Do not read/write to it directly.
*/
struct AppFdSlot {
struct AppFile file;
bool in_use;
bool ever_used;
};
/**
* Every app instance's fd table (see AppInstanceRecord). `mutex` guards `slots` only; it is
* never held while calling into an AppFile's ops (dispatch copies the AppFile out under the lock
* first), since those calls belong to independently synchronized objects (AppStream's own
* mutex/event group) and may run concurrently with a close()/bind() replacing an unrelated slot.
*
* `shutting_down` is set under `mutex` by app_fd_table_teardown() before it destructs `mutex`,
* and checked (also under `mutex`) by every other entry point. Callers are expected to
* serialize against teardown externally (see app_stream_subscribe()/app_stream_unsubscribe()),
* so a check() failure here means that external synchronization broke, not a condition to
* handle gracefully.
*
* @warning This is internal data. Do not read/write to it directly.
*/
struct AppFdTable {
struct AppFdSlot slots[APP_MAX_FDS];
struct Mutex mutex;
bool shutting_down;
};
#ifdef __cplusplus
extern "C" {
#endif
/** Initializes every fd to the null device (see app/private/null_device.h) and constructs `mutex`. */
void app_fd_table_construct(struct AppFdTable* table);
/** Closes every still-open fd (via AppFileOps::close()) and destructs `mutex`. */
void app_fd_table_teardown(struct AppFdTable* table);
/**
* Installs {ops, object} at @a fd, closing whatever was previously there first (the null device
* counts as "previously there" for fds 0-2, so this doubles as their initial stdio binding).
* @retval ERROR_OUT_OF_RANGE @a fd is outside [0, APP_MAX_FDS)
*/
error_t app_fd_table_bind(struct AppFdTable* table, int fd, const struct AppFileOps* ops, void* object);
/**
* Installs {ops, object} at the lowest unused fd >= 3.
* @retval ERROR_RESOURCE the table is full
*/
error_t app_fd_table_allocate(struct AppFdTable* table, const struct AppFileOps* ops, void* object, int* out_fd);
/** @return true and fills @a out_file if @a fd is currently in use, false otherwise. */
bool app_fd_table_get(struct AppFdTable* table, int fd, struct AppFile* out_file);
/**
* Like app_fd_table_get(), but also calls the returned AppFile's AppFileOps::retain() (if any)
* while still holding `mutex`, atomically with the lookup. A caller that gets true back can
* therefore never have @a fd closed/torn down out from under it between this call and actually
* dispatching into the returned AppFile. Caller must call AppFileOps::release() once done
* dispatching.
*/
bool app_fd_table_get_and_retain(struct AppFdTable* table, int fd, struct AppFile* out_file);
/**
* Closes @a fd (via AppFileOps::close()) and frees the slot for reuse.
* @retval ERROR_NOT_FOUND @a fd is out of range or not currently in use
*/
error_t app_fd_table_close(struct AppFdTable* table, int fd);
/**
* @return true if @a fd is currently in use, or was in the past (even if since closed). This is
* whether the table claims @a fd as one of its own, regardless of its current state. False means
* @a fd is a real underlying fd this table has never bound or allocated.
*/
bool app_fd_table_is_app_owned(struct AppFdTable* table, int fd);
#ifdef __cplusplus
}
#endif

View File

@ -3,6 +3,7 @@
#include <app/instance.h>
#include <app/manifest.h>
#include <app/private/fd_table.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/freertos/freertos.h>
@ -49,6 +50,10 @@ struct AppInstanceRecord {
/** This instance's completion signal - see AppCompletionSignal. Set once by
* app_scheduler_start(), never reassigned. */
AppCompletionSignal* completion = nullptr;
/** This instance's fd table. Constructed by start_internal() before insertion into
* AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */
AppFdTable fd_table {};
};
struct AppLedger {

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/file.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @return the shared null-device AppFile: write() discards data and reports success, read()
* reports EOF, close() is a no-op, and it is always reported readable/writable.
*/
const struct AppFile* app_null_file(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/file.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @return the AppFileOps used for stream-backed fd table entries (see app_stream_subscribe()). */
const struct AppFileOps* app_stream_ops(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,151 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/private/fd_table.h>
#include <app/private/null_device.h>
#include <tactility/check.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/error.h>
namespace {
bool fd_in_range(int fd) {
return fd >= 0 && fd < APP_MAX_FDS;
}
bool get_internal(AppFdTable* table, int fd, AppFile* out_file, bool retain) {
if (!fd_in_range(fd)) {
return false;
}
mutex_lock(&table->mutex);
check(!table->shutting_down);
AppFdSlot& slot = table->slots[fd];
bool in_use = slot.in_use;
if (in_use) {
*out_file = slot.file;
if (retain && out_file->ops->retain != nullptr) {
out_file->ops->retain(out_file->object);
}
}
mutex_unlock(&table->mutex);
return in_use;
}
} // namespace
extern "C" {
void app_fd_table_construct(AppFdTable* table) {
const AppFile* null_file = app_null_file();
for (int fd = 0; fd < APP_MAX_FDS; fd++) {
table->slots[fd].in_use = false;
table->slots[fd].ever_used = false;
}
for (int fd = STDIN_FILENO; fd <= STDERR_FILENO; fd++) {
table->slots[fd].file = *null_file;
table->slots[fd].in_use = true;
table->slots[fd].ever_used = true;
}
table->shutting_down = false;
mutex_construct(&table->mutex);
}
void app_fd_table_teardown(AppFdTable* table) {
AppFile closing[APP_MAX_FDS];
int closing_count = 0;
mutex_lock(&table->mutex);
table->shutting_down = true;
for (int fd = 0; fd < APP_MAX_FDS; fd++) {
if (table->slots[fd].in_use) {
closing[closing_count++] = table->slots[fd].file;
table->slots[fd].in_use = false;
}
}
mutex_unlock(&table->mutex);
for (int i = 0; i < closing_count; i++) {
closing[i].ops->close(closing[i].object);
}
mutex_destruct(&table->mutex);
}
error_t app_fd_table_bind(AppFdTable* table, int fd, const AppFileOps* ops, void* object) {
if (!fd_in_range(fd)) {
return ERROR_OUT_OF_RANGE;
}
mutex_lock(&table->mutex);
check(!table->shutting_down);
AppFdSlot& slot = table->slots[fd];
AppFile old = slot.file;
bool had_old = slot.in_use;
slot.file = { .ops = ops, .object = object };
slot.in_use = true;
slot.ever_used = true;
mutex_unlock(&table->mutex);
if (had_old) {
old.ops->close(old.object);
}
return ERROR_NONE;
}
error_t app_fd_table_allocate(AppFdTable* table, const AppFileOps* ops, void* object, int* out_fd) {
mutex_lock(&table->mutex);
check(!table->shutting_down);
for (int fd = 3; fd < APP_MAX_FDS; fd++) {
AppFdSlot& slot = table->slots[fd];
if (!slot.in_use) {
slot.file = { .ops = ops, .object = object };
slot.in_use = true;
slot.ever_used = true;
mutex_unlock(&table->mutex);
*out_fd = fd;
return ERROR_NONE;
}
}
mutex_unlock(&table->mutex);
return ERROR_RESOURCE;
}
bool app_fd_table_get(AppFdTable* table, int fd, AppFile* out_file) {
return get_internal(table, fd, out_file, /*retain=*/false);
}
bool app_fd_table_get_and_retain(AppFdTable* table, int fd, AppFile* out_file) {
return get_internal(table, fd, out_file, /*retain=*/true);
}
error_t app_fd_table_close(AppFdTable* table, int fd) {
if (!fd_in_range(fd)) {
return ERROR_NOT_FOUND;
}
mutex_lock(&table->mutex);
check(!table->shutting_down);
AppFdSlot& slot = table->slots[fd];
bool in_use = slot.in_use;
AppFile old = slot.file;
slot.in_use = false;
mutex_unlock(&table->mutex);
if (!in_use) {
return ERROR_NOT_FOUND;
}
return old.ops->close(old.object);
}
bool app_fd_table_is_app_owned(AppFdTable* table, int fd) {
if (!fd_in_range(fd)) {
return false;
}
mutex_lock(&table->mutex);
bool owned = table->slots[fd].in_use || table->slots[fd].ever_used;
mutex_unlock(&table->mutex);
return owned;
}
} // extern "C"

View File

@ -37,9 +37,9 @@ std::string last_path_segment(const std::string& path) {
return index == std::string::npos ? path : path.substr(index + 1);
}
// Rejects absolute paths and ".." components, so a crafted tar entry can't extract outside destination_path (CWE-22).
// Rejects ".." components, so a crafted tar entry can't extract outside destination_path (CWE-22).
bool is_tar_entry_path_safe(const std::string& path) {
if (path.empty() || path.front() == '/') {
if (path.empty()) {
return false;
}

View File

@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/io.h>
#include <app/private/fd_table.h>
#include <app/private/ledger.h>
#include <app/scheduler.h>
#include <cerrno>
#ifdef ESP_PLATFORM
extern "C" {
ssize_t __real_read(int fd, void* buffer, size_t size);
ssize_t __real_write(int fd, const void* buffer, size_t size);
int __real_close(int fd);
}
#else
#include <unistd.h>
#endif
namespace {
// NULL for a caller not running as an app instance (e.g. a kernel service task). @a fd is then
// a real underlying fd, handled by the caller falling through to the real syscall.
AppFdTable* current_app_fd_table() {
AppInstanceId app_id = app_scheduler_current_app_id();
if (app_id == 0) {
return nullptr;
}
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
auto iterator = ledger.instances.find(app_id);
AppFdTable* table = (iterator != ledger.instances.end()) ? &iterator->second.fd_table : nullptr;
mutex_unlock(&ledger.mutex);
return table;
}
} // namespace
extern "C" {
ssize_t app_io_read(int fd, void* buffer, size_t size) {
AppFdTable* table = current_app_fd_table();
AppFile file {};
if (table != nullptr && app_fd_table_get_and_retain(table, fd, &file)) {
ssize_t result = file.ops->read(file.object, buffer, size);
if (file.ops->release != nullptr) {
file.ops->release(file.object);
}
return result;
}
// @a fd isn't currently bound. If this table has never touched it either, it's a real
// underlying fd (e.g. from fopen()/open(), which app-module never intercepts; see app/io.h),
// so fall through. Otherwise it's one of ours that's already been closed: a real EBADF, not
// a real fd to hand to the platform (which could belong to something else entirely by now).
if (table != nullptr && app_fd_table_is_app_owned(table, fd)) {
errno = EBADF;
return -1;
}
#ifdef ESP_PLATFORM
return __real_read(fd, buffer, size);
#else
return ::read(fd, buffer, size);
#endif
}
ssize_t app_io_write(int fd, const void* buffer, size_t size) {
AppFdTable* table = current_app_fd_table();
AppFile file {};
if (table != nullptr && app_fd_table_get_and_retain(table, fd, &file)) {
ssize_t result = file.ops->write(file.object, buffer, size);
if (file.ops->release != nullptr) {
file.ops->release(file.object);
}
return result;
}
if (table != nullptr && app_fd_table_is_app_owned(table, fd)) {
errno = EBADF;
return -1;
}
#ifdef ESP_PLATFORM
return __real_write(fd, buffer, size);
#else
return ::write(fd, buffer, size);
#endif
}
int app_io_close(int fd) {
AppFdTable* table = current_app_fd_table();
if (table != nullptr) {
error_t result = app_fd_table_close(table, fd);
if (result == ERROR_NONE) {
return 0;
}
if (app_fd_table_is_app_owned(table, fd)) {
errno = EBADF;
return -1;
}
}
#ifdef ESP_PLATFORM
return __real_close(fd);
#else
return ::close(fd);
#endif
}
} // extern "C"

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/manager.h>
#include <app/metadata.h>
#include <app/private/fd_table.h>
#include <app/private/fs.h>
#include <app/private/ledger.h>
#include <app/private/scheduler.h>
@ -89,8 +90,15 @@ char** copy_arguments(int argc, const char* const argv[]) {
// Takes ownership of argv (already a deep copy, or NULL/argc==0) regardless of outcome -
// app_scheduler_start() frees it on any failure path, and the spawned task frees it once its
// run() returns.
error_t start_internal(const char* id, AppInstanceId parent_instance_id, int argc, char* argv[], AppInstanceId* out_app_instance_id) {
// run() returns. @a bindings (@a binding_count entries, may be NULL/0) are subscribed into the
// new instance's fd table before app_scheduler_start() is called, so they're in place before its
// task begins executing (see app_manager_start_with_streams()).
error_t start_internal(const char* id, AppInstanceId parent_instance_id, int argc, char* argv[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
if (binding_count != 0 && bindings == nullptr) {
app_ledger_free_arguments(argc, argv);
return ERROR_INVALID_ARGUMENT;
}
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
@ -106,13 +114,42 @@ error_t start_internal(const char* id, AppInstanceId parent_instance_id, int arg
AppInstanceRecord record { .id = target_id, .manifest = manifest, .state = APP_INSTANCE_STATE_STARTING, .task = nullptr };
record.parent_id = parent_instance_id;
ledger.instances[target_id] = record;
// Constructed on the map-resident copy, not the local `record` about to go out of scope.
// AppFdTable::fds[] entries point into AppFdTable::slots[] by address (see fd_table.h), so
// constructing before the copy above would leave them pointing at stack storage.
app_fd_table_construct(&ledger.instances[target_id].fd_table);
mutex_unlock(&ledger.mutex);
LOG_I(TAG, "[instance %d] starting %s with parent %d", target_id, manifest->id, parent_instance_id);
for (size_t i = 0; i < binding_count; i++) {
error_t bind_result = app_stream_subscribe(bindings[i].stream, bindings[i].buffer, bindings[i].buffer_capacity, bindings[i].event_group, target_id, bindings[i].producer_fd);
if (bind_result != ERROR_NONE) {
LOG_E(TAG, "[instance %d] Failed to bind stream at fd %d: %s", target_id, bindings[i].producer_fd, error_to_string(bind_result));
// Undo bindings[0..i): app_fd_table_teardown() below only closes each stream. It
// doesn't release the event bits app_stream_subscribe() claimed or destruct
// stream->internal.mutex; only app_stream_unsubscribe() does that.
for (size_t j = 0; j < i; j++) {
app_stream_unsubscribe(bindings[j].stream);
}
mutex_lock(&ledger.mutex);
app_fd_table_teardown(&ledger.instances[target_id].fd_table);
ledger.instances.erase(target_id);
mutex_unlock(&ledger.mutex);
app_ledger_free_arguments(argc, argv);
return bind_result;
}
}
error_t error = app_scheduler_start(target_id, manifest->location, manifest->stack, argc, argv);
if (error != ERROR_NONE) {
// Every binding succeeded before app_scheduler_start() failed. Unsubscribe all of them,
// same reasoning as the bind-failure path above.
for (size_t j = 0; j < binding_count; j++) {
app_stream_unsubscribe(bindings[j].stream);
}
mutex_lock(&ledger.mutex);
app_fd_table_teardown(&ledger.instances[target_id].fd_table);
ledger.instances.erase(target_id);
mutex_unlock(&ledger.mutex);
LOG_I(TAG, "[instance %d] Failed to start: %s", target_id, error_to_string(error));
@ -126,15 +163,19 @@ error_t start_internal(const char* id, AppInstanceId parent_instance_id, int arg
} // namespace
error_t app_manager_start(const char* id, AppInstanceId* out_app_instance_id) {
return start_internal(id, 0, 0, nullptr, out_app_instance_id);
return start_internal(id, 0, 0, nullptr, nullptr, 0, out_app_instance_id);
}
error_t app_manager_start_with_parameters(const char* id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
return start_internal(id, 0, argc, copy_arguments(argc, argv), out_app_instance_id);
return start_internal(id, 0, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id);
}
error_t app_manager_start_for_result(const char* id, AppInstanceId parent_instance_id, int argc, const char* const argv[], AppInstanceId* out_app_instance_id) {
return start_internal(id, parent_instance_id, argc, copy_arguments(argc, argv), out_app_instance_id);
return start_internal(id, parent_instance_id, argc, copy_arguments(argc, argv), nullptr, 0, out_app_instance_id);
}
error_t app_manager_start_with_streams(const char* id, const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
return start_internal(id, 0, 0, nullptr, bindings, binding_count, out_app_instance_id);
}
error_t app_manager_stop(AppInstanceId app_instance_id) {

View File

@ -1,11 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/event.h>
#include <app/install.h>
#include <app/io.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/metadata.h>
#include <app/paths.h>
#include <app/scheduler.h>
#include <app/stream.h>
#include <service/manager.h>
@ -27,10 +29,15 @@ static const ModuleSymbol SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(app_get_install_path),
DEFINE_MODULE_SYMBOL(app_install),
DEFINE_MODULE_SYMBOL(app_uninstall),
// app/io
DEFINE_MODULE_SYMBOL(app_io_read),
DEFINE_MODULE_SYMBOL(app_io_write),
DEFINE_MODULE_SYMBOL(app_io_close),
// app/manager
DEFINE_MODULE_SYMBOL(app_manager_start),
DEFINE_MODULE_SYMBOL(app_manager_start_with_parameters),
DEFINE_MODULE_SYMBOL(app_manager_start_for_result),
DEFINE_MODULE_SYMBOL(app_manager_start_with_streams),
DEFINE_MODULE_SYMBOL(app_manager_stop),
DEFINE_MODULE_SYMBOL(app_manager_get_state),
DEFINE_MODULE_SYMBOL(app_manager_find_manifest),
@ -53,6 +60,13 @@ static const ModuleSymbol SYMBOLS[] = {
DEFINE_MODULE_SYMBOL(app_paths_get_assets_path),
// app/scheduler
DEFINE_MODULE_SYMBOL(app_scheduler_current_app_id),
// app/stream
DEFINE_MODULE_SYMBOL(app_stream_subscribe),
DEFINE_MODULE_SYMBOL(app_stream_unsubscribe),
DEFINE_MODULE_SYMBOL(app_stream_await),
DEFINE_MODULE_SYMBOL(app_stream_read),
DEFINE_MODULE_SYMBOL(app_stream_write),
DEFINE_MODULE_SYMBOL(app_stream_close),
// terminator
MODULE_SYMBOL_TERMINATOR,
};

View File

@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/private/null_device.h>
namespace {
ssize_t null_read(void*, void*, size_t) {
return 0; // EOF
}
ssize_t null_write(void*, const void* buffer, size_t size) {
(void)buffer;
return static_cast<ssize_t>(size); // discarded, reported as fully written
}
error_t null_close(void*) {
return ERROR_NONE;
}
error_t null_await(void*, AppFileWait, TickType_t) {
return ERROR_NONE; // always ready
}
uint32_t null_poll(void*) {
return APP_FILE_READABLE | APP_FILE_WRITABLE;
}
const AppFileOps NULL_OPS = {
.read = null_read,
.write = null_write,
.close = null_close,
.await = null_await,
.poll = null_poll,
};
constexpr AppFile NULL_FILE = { .ops = &NULL_OPS, .object = nullptr };
} // namespace
extern "C" {
const AppFile* app_null_file(void) {
return &NULL_FILE;
}
} // extern "C"

View File

@ -2,9 +2,12 @@
#include <app/instance.h>
#include <app/loader.h>
#include <app/private/event.h>
#include <app/private/fd_table.h>
#include <app/private/ledger.h>
#include <app/private/scheduler.h>
#include <app/private/stream_internal.h>
#include <app/scheduler.h>
#include <app/stream.h>
#include <service/instance.h>
#include <service/manager.h>
@ -98,6 +101,14 @@ void set_task(AppInstanceId app_instance_id, TaskHandle_t task) {
auto iterator = ledger.instances.find(app_instance_id);
if (iterator != ledger.instances.end()) {
iterator->second.task = task;
// Streams bound before this instance's task existed (app_manager_start_with_streams())
// only got producer_task filled in as NULL at subscribe time. Backfill it now.
AppFdTable& fd_table = iterator->second.fd_table;
for (auto& slot : fd_table.slots) {
if (slot.in_use && slot.file.ops == app_stream_ops()) {
static_cast<AppStream*>(slot.file.object)->producer_task = task;
}
}
}
mutex_unlock(&ledger.mutex);
}
@ -218,8 +229,14 @@ void app_task_main(void* context) {
// Erase the ledger entry before self-deleting - see "Reap self-terminated app tasks":
// nothing else is guaranteed to ever call app_scheduler_stop() for this instance (the
// common case is the app just closing itself), so this can't wait for that to happen.
// Every non-null fd is closed here too. Stream-backed entries wake/mark closed whoever is
// on the other end (e.g. a parent reading this instance's stdout).
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
auto fd_table_iterator = ledger.instances.find(app_instance_id);
if (fd_table_iterator != ledger.instances.end()) {
app_fd_table_teardown(&fd_table_iterator->second.fd_table);
}
ledger.instances.erase(app_instance_id);
mutex_unlock(&ledger.mutex);

View File

@ -0,0 +1,313 @@
// SPDX-License-Identifier: Apache-2.0
#include <app/stream.h>
#include <app/private/fd_table.h>
#include <app/private/ledger.h>
#include <app/private/stream_internal.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/error.h>
namespace {
// Caller must hold stream->mutex.
bool is_readable_locked(AppStream* stream) {
return stream->buffer.count > 0 || stream->closed;
}
// Caller must hold stream->mutex.
bool is_writable_locked(AppStream* stream) {
return stream->buffer.count < stream->buffer.capacity || stream->closed;
}
// Brackets one AppFileOps call against `stream` for app_stream_unsubscribe()'s drain wait: a
// call already blocked in app_stream_await() when unsubscribe starts only wakes, it doesn't
// vanish, so unsubscribe must know it's still running before destructing stream->mutex.
class StreamOperationGuard {
public:
explicit StreamOperationGuard(AppStream* stream) : stream_(stream) {
mutex_lock(&stream_->mutex);
stream_->active_operations++;
mutex_unlock(&stream_->mutex);
}
~StreamOperationGuard() {
mutex_lock(&stream_->mutex);
stream_->active_operations--;
mutex_unlock(&stream_->mutex);
}
StreamOperationGuard(const StreamOperationGuard&) = delete;
StreamOperationGuard& operator=(const StreamOperationGuard&) = delete;
private:
AppStream* stream_;
};
ssize_t stream_file_read(void* object, void* buffer, size_t size) {
auto* stream = static_cast<AppStream*>(object);
StreamOperationGuard guard(stream);
while (true) {
if (app_stream_await(stream, APP_FILE_WAIT_READABLE, portMAX_DELAY) != ERROR_NONE) {
return -1;
}
size_t read = app_stream_read(stream, buffer, size);
if (read > 0) {
return static_cast<ssize_t>(read);
}
mutex_lock(&stream->mutex);
bool is_closed = stream->closed;
mutex_unlock(&stream->mutex);
if (is_closed) {
return 0; // EOF: readable-because-closed, and nothing left buffered
}
// Woke readable but another party drained it first. Re-await rather than assume EOF;
// this is a defensive fallback since AppStream expects a single reader.
}
}
ssize_t stream_file_write(void* object, const void* buffer, size_t size) {
auto* stream = static_cast<AppStream*>(object);
StreamOperationGuard guard(stream);
if (app_stream_await(stream, APP_FILE_WAIT_WRITABLE, portMAX_DELAY) != ERROR_NONE) {
return -1;
}
// app_stream_write() itself refuses to copy anything once closed (checked under the same
// lock as the copy, so a close() racing right after this await() can't slip bytes in), and
// reports that as 0, indistinguishable here from "woke writable, nothing to copy" without
// checking closed separately.
size_t written = app_stream_write(stream, buffer, size);
if (written > 0) {
return static_cast<ssize_t>(written);
}
mutex_lock(&stream->mutex);
bool is_closed = stream->closed;
mutex_unlock(&stream->mutex);
return is_closed ? -1 : 0; // broken pipe, or a spurious wake with nothing to copy
}
error_t stream_file_close(void* object) {
auto* stream = static_cast<AppStream*>(object);
StreamOperationGuard guard(stream);
return app_stream_close(stream);
}
error_t stream_file_await(void* object, AppFileWait wait, TickType_t timeout) {
auto* stream = static_cast<AppStream*>(object);
StreamOperationGuard guard(stream);
return app_stream_await(stream, wait, timeout);
}
uint32_t stream_file_poll(void* object) {
auto* stream = static_cast<AppStream*>(object);
StreamOperationGuard guard(stream);
mutex_lock(&stream->mutex);
uint32_t bits = 0;
if (is_readable_locked(stream)) {
bits |= APP_FILE_READABLE;
}
if (is_writable_locked(stream)) {
bits |= APP_FILE_WRITABLE;
}
mutex_unlock(&stream->mutex);
return bits;
}
// Fused with app_fd_table_get_and_retain()'s own lock, so a caller that gets a live AppFile back
// is already counted here before it can be paused and raced by app_stream_unsubscribe()'s drain
// wait (see StreamOperationGuard above for the equivalent per-call bracketing).
void stream_file_retain(void* object) {
auto* stream = static_cast<AppStream*>(object);
mutex_lock(&stream->mutex);
stream->active_operations++;
mutex_unlock(&stream->mutex);
}
void stream_file_release(void* object) {
auto* stream = static_cast<AppStream*>(object);
mutex_lock(&stream->mutex);
stream->active_operations--;
mutex_unlock(&stream->mutex);
}
constexpr AppFileOps STREAM_OPS = {
.read = stream_file_read,
.write = stream_file_write,
.close = stream_file_close,
.await = stream_file_await,
.poll = stream_file_poll,
.retain = stream_file_retain,
.release = stream_file_release,
};
} // namespace
extern "C" {
const AppFileOps* app_stream_ops(void) {
return &STREAM_OPS;
}
error_t app_stream_subscribe(AppStream* stream, void* buffer, size_t buffer_capacity, TaskEventGroup* event_group, AppInstanceId producer_id, int producer_fd) {
if (producer_fd < 0 || producer_fd >= APP_MAX_FDS) {
return ERROR_OUT_OF_RANGE;
}
uint32_t readable_bit;
error_t claim_result = task_event_group_claim_bit(event_group, &readable_bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
uint32_t writable_bit;
claim_result = task_event_group_claim_bit(event_group, &writable_bit);
if (claim_result != ERROR_NONE) {
task_event_group_release_bit(event_group, readable_bit);
return claim_result;
}
stream->producer_id = producer_id;
stream->producer_fd = producer_fd;
stream->event_group = event_group;
stream->readable_bit = readable_bit;
stream->writable_bit = writable_bit;
stream->buffer.data = static_cast<uint8_t*>(buffer);
stream->buffer.capacity = buffer_capacity;
stream->buffer.read_pos = 0;
stream->buffer.write_pos = 0;
stream->buffer.count = 0;
stream->closed = false;
stream->active_operations = 0;
mutex_construct(&stream->mutex);
// Held across the fd-table lookup and bind so this can't race app_fd_table_teardown():
// both teardown call sites (scheduler.cpp, manager.cpp) hold this same ledger mutex for
// their whole teardown call, and app_fd_table_bind()/close()/get() check for it (see
// fd_table.h's AppFdTable::shutting_down).
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
auto iterator = ledger.instances.find(producer_id);
if (iterator == ledger.instances.end()) {
mutex_unlock(&ledger.mutex);
mutex_destruct(&stream->mutex);
task_event_group_release_bit(event_group, readable_bit);
task_event_group_release_bit(event_group, writable_bit);
return ERROR_NOT_FOUND;
}
stream->producer_task = iterator->second.task;
error_t bind_result = app_fd_table_bind(&iterator->second.fd_table, producer_fd, &STREAM_OPS, stream);
mutex_unlock(&ledger.mutex);
if (bind_result != ERROR_NONE) {
mutex_destruct(&stream->mutex);
task_event_group_release_bit(event_group, readable_bit);
task_event_group_release_bit(event_group, writable_bit);
}
return bind_result;
}
error_t app_stream_unsubscribe(AppStream* stream) {
// Held across the fd-table lookup, get, and close. See app_stream_subscribe()'s own
// comment on why this must stay exclusive with app_fd_table_teardown().
auto& ledger = app_ledger();
mutex_lock(&ledger.mutex);
auto iterator = ledger.instances.find(stream->producer_id);
if (iterator != ledger.instances.end()) {
AppFdTable* table = &iterator->second.fd_table;
AppFile current {};
if (app_fd_table_get(table, stream->producer_fd, &current) && current.object == stream) {
app_fd_table_close(table, stream->producer_fd); // -> stream_file_close() -> app_stream_close()
}
}
mutex_unlock(&ledger.mutex);
app_stream_close(stream); // idempotent; wakes anyone already blocked in app_stream_await()
// Waking a blocked call doesn't mean it has finished. It still has to get scheduled, notice
// it's closed, and return. Wait for that before destructing mutex/bits below, mirroring
// scheduler.cpp's reap_self()/reaper_task_main() waiting out a task before freeing its stack.
while (true) {
mutex_lock(&stream->mutex);
int active_operations = stream->active_operations;
mutex_unlock(&stream->mutex);
if (active_operations == 0) {
break;
}
taskYIELD();
}
task_event_group_release_bit(stream->event_group, stream->readable_bit);
task_event_group_release_bit(stream->event_group, stream->writable_bit);
mutex_destruct(&stream->mutex);
return ERROR_NONE;
}
error_t app_stream_await(AppStream* stream, AppFileWait wait, TickType_t timeout) {
mutex_lock(&stream->mutex);
bool ready = (wait == APP_FILE_WAIT_READABLE) ? is_readable_locked(stream) : is_writable_locked(stream);
mutex_unlock(&stream->mutex);
if (ready) {
return ERROR_NONE;
}
uint32_t bit = (wait == APP_FILE_WAIT_READABLE) ? stream->readable_bit : stream->writable_bit;
return task_event_group_wait(stream->event_group, bit, /*await_all=*/false, nullptr, timeout);
}
size_t app_stream_read(AppStream* stream, void* buffer, size_t buffer_size) {
mutex_lock(&stream->mutex);
AppStreamBuffer& ring = stream->buffer;
size_t to_copy = buffer_size < ring.count ? buffer_size : ring.count;
for (size_t i = 0; i < to_copy; i++) {
static_cast<uint8_t*>(buffer)[i] = ring.data[(ring.read_pos + i) % ring.capacity];
}
if (ring.capacity > 0) {
ring.read_pos = (ring.read_pos + to_copy) % ring.capacity;
}
ring.count -= to_copy;
TaskEventGroup* event_group = stream->event_group;
uint32_t writable_bit = stream->writable_bit;
mutex_unlock(&stream->mutex);
if (to_copy > 0) {
task_event_group_signal(event_group, writable_bit); // space freed up
}
return to_copy;
}
size_t app_stream_write(AppStream* stream, const void* buffer, size_t buffer_size) {
mutex_lock(&stream->mutex);
if (stream->closed) {
mutex_unlock(&stream->mutex);
return 0;
}
AppStreamBuffer& ring = stream->buffer;
size_t available = ring.capacity - ring.count;
size_t to_copy = buffer_size < available ? buffer_size : available;
for (size_t i = 0; i < to_copy; i++) {
ring.data[(ring.write_pos + i) % ring.capacity] = static_cast<const uint8_t*>(buffer)[i];
}
if (ring.capacity > 0) {
ring.write_pos = (ring.write_pos + to_copy) % ring.capacity;
}
ring.count += to_copy;
TaskEventGroup* event_group = stream->event_group;
uint32_t readable_bit = stream->readable_bit;
mutex_unlock(&stream->mutex);
if (to_copy > 0) {
task_event_group_signal(event_group, readable_bit); // data became available
}
return to_copy;
}
error_t app_stream_close(AppStream* stream) {
mutex_lock(&stream->mutex);
stream->closed = true;
TaskEventGroup* event_group = stream->event_group;
uint32_t readable_bit = stream->readable_bit;
uint32_t writable_bit = stream->writable_bit;
mutex_unlock(&stream->mutex);
task_event_group_signal(event_group, readable_bit);
task_event_group_signal(event_group, writable_bit);
return ERROR_NONE;
}
} // extern "C"

View File

@ -0,0 +1,294 @@
// SPDX-License-Identifier: Apache-2.0
#include "doctest.h"
#include <app/io.h>
#include <app/loader.h>
#include <app/manager.h>
#include <app/scheduler.h>
#include <app/stream.h>
#include <service/manager.h>
#include <tactility/delay.h>
#include <fcntl.h>
#include <unistd.h>
#include <atomic>
#include <cstring>
#include <vector>
extern ServiceManifest app_internal_loader_service_manifest;
namespace {
// See manager_test.cpp's own copy of this helper for why this checks the registry directly
// rather than a per-translation-unit static bool.
void ensure_memory_loader_registered() {
if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) {
service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true);
}
}
bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ms) {
uint32_t waited = 0;
while (waited < timeout_ms) {
if (app_manager_get_state(id) == target) {
return true;
}
delay_millis(10);
waited += 10;
}
return app_manager_get_state(id) == target;
}
std::atomic<ssize_t> g_stdio_write_result { -2 };
std::atomic<ssize_t> g_stdio_read_result { -2 };
int32_t unbound_stdio_app_main(int, char*[]) {
g_stdio_write_result.store(app_io_write(STDOUT_FILENO, "x", 1), std::memory_order_release);
uint8_t buffer[1];
g_stdio_read_result.store(app_io_read(STDIN_FILENO, buffer, sizeof(buffer)), std::memory_order_release);
return 0;
}
int32_t stdout_writer_app_main(int, char*[]) {
const char message[] = "hello";
size_t sent = 0;
while (sent < sizeof(message) - 1) {
ssize_t written = app_io_write(STDOUT_FILENO, message + sent, sizeof(message) - 1 - sent);
if (written < 0) {
break;
}
sent += static_cast<size_t>(written);
}
return 0;
}
std::atomic<bool> g_blocked_writer_saw_error { false };
std::atomic<bool> g_blocked_writer_done { false };
int32_t blocked_writer_app_main(int, char*[]) {
const char message[] = "0123456789"; // larger than the test's 4-byte stream capacity
size_t sent = 0;
while (sent < sizeof(message) - 1) {
ssize_t written = app_io_write(STDOUT_FILENO, message + sent, sizeof(message) - 1 - sent);
if (written < 0) {
g_blocked_writer_saw_error.store(true, std::memory_order_release);
break;
}
sent += static_cast<size_t>(written);
}
g_blocked_writer_done.store(true, std::memory_order_release);
return 0;
}
std::atomic<ssize_t> g_real_file_write_result { -2 };
std::atomic<ssize_t> g_real_file_read_result { -2 };
std::atomic<bool> g_real_file_read_matches { false };
std::atomic<int> g_real_file_close_result { -2 };
// A real file fd from a bare open() call: app-module never intercepts open(), so this fd is
// never bound/allocated in the app's own fd table. app_io_read/write/close() must still pass it
// straight through to the real syscall instead of treating it as an unknown app-level fd.
int32_t real_file_io_app_main(int, char*[]) {
const char* path = "/tmp/tactility_app_io_passthrough_test.txt";
int real_fd = ::open(path, O_CREAT | O_TRUNC | O_RDWR, 0600);
if (real_fd < 0) {
return 0;
}
g_real_file_write_result.store(app_io_write(real_fd, "hi", 2), std::memory_order_release);
::lseek(real_fd, 0, SEEK_SET);
char buffer[2] = {};
ssize_t read_result = app_io_read(real_fd, buffer, sizeof(buffer));
g_real_file_read_result.store(read_result, std::memory_order_release);
g_real_file_read_matches.store(read_result == 2 && buffer[0] == 'h' && buffer[1] == 'i', std::memory_order_release);
g_real_file_close_result.store(app_io_close(real_fd), std::memory_order_release);
::unlink(path);
return 0;
}
std::atomic<int> g_double_close_first_result { -2 };
std::atomic<int> g_double_close_second_result { -2 };
std::atomic<ssize_t> g_write_after_close_result { -2 };
// A second close() of an already-closed app fd, and a write() after that, must both report
// EBADF, never fall through to the platform syscall, which by then could be operating on a real
// fd that fd number was recycled for (e.g. the process's real stdout).
int32_t double_close_app_main(int, char*[]) {
g_double_close_first_result.store(app_io_close(STDOUT_FILENO), std::memory_order_release);
g_double_close_second_result.store(app_io_close(STDOUT_FILENO), std::memory_order_release);
g_write_after_close_result.store(app_io_write(STDOUT_FILENO, "x", 1), std::memory_order_release);
return 0;
}
} // namespace
TEST_CASE("an app's stdio fds default to the null device: write succeeds and discards, read reports EOF") {
ensure_memory_loader_registered();
g_stdio_write_result.store(-2, std::memory_order_relaxed);
g_stdio_read_result.store(-2, std::memory_order_relaxed);
AppManifest manifest { "test.io.unbound", "Unbound", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(unbound_stdio_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
AppInstanceId instance_id = 0;
REQUIRE_EQ(app_manager_start("test.io.unbound", &instance_id), ERROR_NONE);
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
CHECK_EQ(g_stdio_write_result.load(std::memory_order_acquire), 1);
CHECK_EQ(g_stdio_read_result.load(std::memory_order_acquire), 0);
app_manager_remove("test.io.unbound");
}
TEST_CASE("app_manager_start_with_streams pipes a child's app_io_write() calls into a parent-owned AppStream, EOF at exit") {
ensure_memory_loader_registered();
AppManifest manifest { "test.io.writer", "Writer", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(stdout_writer_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage[64];
AppStream child_stdout {};
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
AppInstanceId child_id = 0;
REQUIRE_EQ(app_manager_start_with_streams("test.io.writer", &binding, 1, &child_id), ERROR_NONE);
std::vector<uint8_t> received;
while (app_stream_await(&child_stdout, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(1000)) == ERROR_NONE) {
uint8_t chunk[16];
size_t n = app_stream_read(&child_stdout, chunk, sizeof(chunk));
if (n == 0) {
break; // EOF
}
received.insert(received.end(), chunk, chunk + n);
}
REQUIRE_EQ(received.size(), 5u);
CHECK_EQ(std::memcmp(received.data(), "hello", 5), 0);
REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000));
app_stream_unsubscribe(&child_stdout);
task_event_group_destruct(&event_group);
app_manager_remove("test.io.writer");
}
TEST_CASE("a write blocked on a full stream wakes with an error once the consumer closes it") {
ensure_memory_loader_registered();
g_blocked_writer_saw_error.store(false, std::memory_order_relaxed);
g_blocked_writer_done.store(false, std::memory_order_relaxed);
AppManifest manifest { "test.io.blocked", "Blocked", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(blocked_writer_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage[4]; // smaller than the 10 bytes blocked_writer_app_main sends
AppStream child_stdout {};
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
AppInstanceId child_id = 0;
REQUIRE_EQ(app_manager_start_with_streams("test.io.blocked", &binding, 1, &child_id), ERROR_NONE);
// Never drained: the child fills the 4-byte buffer and blocks awaiting space for the rest.
delay_millis(200);
CHECK_FALSE(g_blocked_writer_done.load(std::memory_order_acquire));
// Unblocks the writer without touching child_stdout's sync primitives, safe even while it
// may still be blocked in app_stream_await() (unlike app_stream_unsubscribe()).
app_stream_close(&child_stdout);
REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000));
CHECK(g_blocked_writer_saw_error.load(std::memory_order_acquire));
// Only safe now that the child's task (the only other party that could be blocked on this
// stream) has fully exited.
app_stream_unsubscribe(&child_stdout);
task_event_group_destruct(&event_group);
app_manager_remove("test.io.blocked");
}
TEST_CASE("app_stream_unsubscribe is safe to call while a write is actively blocked") {
ensure_memory_loader_registered();
g_blocked_writer_saw_error.store(false, std::memory_order_relaxed);
g_blocked_writer_done.store(false, std::memory_order_relaxed);
AppManifest manifest { "test.io.unsub_race", "UnsubRace", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(blocked_writer_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage[4]; // smaller than the 10 bytes blocked_writer_app_main sends
AppStream child_stdout {};
AppStreamBinding binding { STDOUT_FILENO, &child_stdout, storage, sizeof(storage), &event_group };
AppInstanceId child_id = 0;
REQUIRE_EQ(app_manager_start_with_streams("test.io.unsub_race", &binding, 1, &child_id), ERROR_NONE);
// Give the child time to fill the 4-byte buffer and block inside app_io_write(), already
// dispatched through app_fd_table_get_and_retain() and currently waiting in
// app_stream_await(). This is the exact state app_stream_unsubscribe() must be safe to run
// against, with no prior app_stream_close() or wait for the child to stop first.
delay_millis(200);
REQUIRE_FALSE(g_blocked_writer_done.load(std::memory_order_acquire));
// Regression: unsubscribing directly here used to be able to destruct
// stream->mutex while the blocked write above was still executing against it.
REQUIRE_EQ(app_stream_unsubscribe(&child_stdout), ERROR_NONE);
REQUIRE(wait_for_state(child_id, APP_INSTANCE_STATE_STOPPED, 1000));
CHECK(g_blocked_writer_saw_error.load(std::memory_order_acquire));
task_event_group_destruct(&event_group);
app_manager_remove("test.io.unsub_race");
}
TEST_CASE("app_io_read/write/close pass through a real file fd app-module never bound") {
ensure_memory_loader_registered();
g_real_file_write_result.store(-2, std::memory_order_relaxed);
g_real_file_read_result.store(-2, std::memory_order_relaxed);
g_real_file_read_matches.store(false, std::memory_order_relaxed);
g_real_file_close_result.store(-2, std::memory_order_relaxed);
AppManifest manifest { "test.io.real_file", "RealFile", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(real_file_io_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
AppInstanceId instance_id = 0;
REQUIRE_EQ(app_manager_start("test.io.real_file", &instance_id), ERROR_NONE);
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
CHECK_EQ(g_real_file_write_result.load(std::memory_order_acquire), 2);
CHECK_EQ(g_real_file_read_result.load(std::memory_order_acquire), 2);
CHECK(g_real_file_read_matches.load(std::memory_order_acquire));
CHECK_EQ(g_real_file_close_result.load(std::memory_order_acquire), 0);
app_manager_remove("test.io.real_file");
}
TEST_CASE("closing an already-closed app fd reports EBADF instead of falling through to the platform") {
ensure_memory_loader_registered();
g_double_close_first_result.store(-2, std::memory_order_relaxed);
g_double_close_second_result.store(-2, std::memory_order_relaxed);
g_write_after_close_result.store(-2, std::memory_order_relaxed);
AppManifest manifest { "test.io.double_close", "DoubleClose", APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(double_close_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
AppInstanceId instance_id = 0;
REQUIRE_EQ(app_manager_start("test.io.double_close", &instance_id), ERROR_NONE);
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_STOPPED, 1000));
CHECK_EQ(g_double_close_first_result.load(std::memory_order_acquire), 0);
CHECK_EQ(g_double_close_second_result.load(std::memory_order_acquire), -1);
CHECK_EQ(g_write_after_close_result.load(std::memory_order_acquire), -1);
app_manager_remove("test.io.double_close");
}

View File

@ -128,12 +128,12 @@ void ensure_fake_loader_registered() {
// app-module's real APP_LOCATION_MEMORY loader (source/app_internal_loader.cpp) - not a fake,
// since it has no platform dependency and is exactly what a statically-linked app would go
// through.
// through. Checks the registry directly rather than a per-translation-unit static bool: other
// test files (stream_test.cpp, io_test.cpp) register the same manifest the same way, and
// doctest doesn't guarantee which file's tests run first.
void ensure_memory_loader_registered() {
static bool registered = false;
if (!registered) {
CHECK_EQ(service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true), ERROR_NONE);
registered = true;
if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) {
service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true);
}
}

View File

@ -0,0 +1,158 @@
// SPDX-License-Identifier: Apache-2.0
#include "doctest.h"
#include <app/event.h>
#include <app/loader.h>
#include <app/manager.h>
#include <app/scheduler.h>
#include <app/stream.h>
#include <service/manager.h>
#include <tactility/delay.h>
#include <cstring>
extern ServiceManifest app_internal_loader_service_manifest;
namespace {
// See manager_test.cpp's own copy of this helper for why this checks the registry directly
// rather than a per-translation-unit static bool.
void ensure_memory_loader_registered() {
if (service_manager_find_instance(APP_LOADER_MEMORY_SERVICE_ID) == nullptr) {
service_manager_add(&app_internal_loader_service_manifest, /*auto_start=*/true);
}
}
// Stays Active, doing nothing, until asked to close. Just an anchor instance for
// app_stream_subscribe() to target; the tests below drive the resulting AppStream directly from
// the test thread, the same way a parent consumes a child's stream without going through its
// own fd table.
int32_t idle_app_main(int, char*[]) {
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
AppEventSubscription sub {};
app_event_subscribe(&sub, &event_group);
while (true) {
if (task_event_group_wait_any(&event_group, nullptr, pdMS_TO_TICKS(5000)) != ERROR_NONE) {
break; // safety net
}
bool done = false;
AppEvent event {};
while (app_event_poll(&sub, &event) == ERROR_NONE) {
if (event.type == APP_EVENT_CLOSE) {
done = true;
break;
}
}
if (done) {
break;
}
}
app_event_unsubscribe(&sub);
task_event_group_destruct(&event_group);
return 0;
}
bool wait_for_state(AppInstanceId id, AppInstanceState target, uint32_t timeout_ms) {
uint32_t waited = 0;
while (waited < timeout_ms) {
if (app_manager_get_state(id) == target) {
return true;
}
delay_millis(10);
waited += 10;
}
return app_manager_get_state(id) == target;
}
AppInstanceId start_idle_app(const char* id) {
ensure_memory_loader_registered();
AppManifest manifest { id, id, APP_CATEGORY_USER, { APP_LOCATION_MEMORY, reinterpret_cast<void*>(idle_app_main) } };
REQUIRE_EQ(app_manager_add(&manifest), ERROR_NONE);
AppInstanceId instance_id = 0;
REQUIRE_EQ(app_manager_start(id, &instance_id), ERROR_NONE);
REQUIRE(wait_for_state(instance_id, APP_INSTANCE_STATE_ACTIVE, 1000));
return instance_id;
}
} // namespace
TEST_CASE("app_stream_read/write move bytes through a ring buffer, respecting capacity") {
AppInstanceId producer_id = start_idle_app("test.stream.ringbuffer");
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage[4];
AppStream stream {};
REQUIRE_EQ(app_stream_subscribe(&stream, storage, sizeof(storage), &event_group, producer_id, 5), ERROR_NONE);
CHECK_EQ(app_stream_write(&stream, "ab", 2), 2u);
CHECK_EQ(app_stream_write(&stream, "cdef", 4), 2u); // only 2 bytes of space left
uint8_t out[8] = {};
CHECK_EQ(app_stream_read(&stream, out, sizeof(out)), 4u);
CHECK_EQ(std::memcmp(out, "abcd", 4), 0);
app_stream_unsubscribe(&stream);
task_event_group_destruct(&event_group);
app_manager_stop(producer_id);
app_manager_remove("test.stream.ringbuffer");
}
TEST_CASE("app_stream_await blocks until data/space is available, and closing forces both bits") {
AppInstanceId producer_id = start_idle_app("test.stream.await");
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage[2];
AppStream stream {};
REQUIRE_EQ(app_stream_subscribe(&stream, storage, sizeof(storage), &event_group, producer_id, 5), ERROR_NONE);
// Nothing written yet, so the readable wait times out.
CHECK_EQ(app_stream_await(&stream, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(50)), ERROR_TIMEOUT);
app_stream_write(&stream, "x", 1);
CHECK_EQ(app_stream_await(&stream, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(50)), ERROR_NONE);
app_stream_close(&stream);
CHECK_EQ(app_stream_await(&stream, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(50)), ERROR_NONE);
CHECK_EQ(app_stream_await(&stream, APP_FILE_WAIT_WRITABLE, pdMS_TO_TICKS(50)), ERROR_NONE);
app_stream_unsubscribe(&stream);
task_event_group_destruct(&event_group);
app_manager_stop(producer_id);
app_manager_remove("test.stream.await");
}
TEST_CASE("app_stream_subscribe over an existing subscription atomically replaces it") {
AppInstanceId producer_id = start_idle_app("test.stream.replace");
TaskEventGroup event_group {};
task_event_group_construct(&event_group);
uint8_t storage_a[4];
uint8_t storage_b[4];
AppStream stream_a {};
REQUIRE_EQ(app_stream_subscribe(&stream_a, storage_a, sizeof(storage_a), &event_group, producer_id, 5), ERROR_NONE);
AppStream stream_b {};
REQUIRE_EQ(app_stream_subscribe(&stream_b, storage_b, sizeof(storage_b), &event_group, producer_id, 5), ERROR_NONE);
// stream_a was replaced: it's now closed (readable reports ready, and reads 0 bytes / EOF),
// even though nobody explicitly unsubscribed it.
CHECK_EQ(app_stream_await(&stream_a, APP_FILE_WAIT_READABLE, pdMS_TO_TICKS(50)), ERROR_NONE);
CHECK_EQ(app_stream_read(&stream_a, storage_a, sizeof(storage_a)), 0u);
app_stream_unsubscribe(&stream_a);
app_stream_unsubscribe(&stream_b);
task_event_group_destruct(&event_group);
app_manager_stop(producer_id);
app_manager_remove("test.stream.replace");
}

View File

@ -61,7 +61,6 @@ if (DEFINED ENV{ESP_IDF_VERSION})
app-esp32-module
platform-esp32
driver
elf_loader
QRCode
esp_http_server
esp_http_client

View File

@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
#ifdef ESP_PLATFORM
#include <app/io.h>
#include <sys/types.h>
extern "C" {
ssize_t __wrap_read(int fd, void* buffer, size_t size) {
return app_io_read(fd, buffer, size);
}
ssize_t __wrap_write(int fd, const void* buffer, size_t size) {
return app_io_write(fd, buffer, size);
}
int __wrap_close(int fd) {
return app_io_close(fd);
}
}
#endif // ESP_PLATFORM

View File

@ -24,6 +24,7 @@ dependencies:
version: "0.0.1"
rules:
- if: "target in [esp32, esp32s3]"
espressif/elf_loader: "1.3.3"
espressif/esp_lcd_touch: "1.2.1"
atanisoft/esp_lcd_touch_xpt2046:
version: "1.0.6"