mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Compare commits
2 Commits
64fb1a9f52
...
7a81b525ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a81b525ed | ||
|
|
6e5e35610b |
@ -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)
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
@ -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()
|
||||
@ -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
|
||||
@ -1,109 +0,0 @@
|
||||
## Description
|
||||
|
||||
[](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)
|
||||
@ -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()
|
||||
@ -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'
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -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)
|
||||
@ -1 +0,0 @@
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_MODULE_PATH})
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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()
|
||||
52
Modules/app-module/include/app/file.h
Normal file
52
Modules/app-module/include/app/file.h
Normal 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
|
||||
35
Modules/app-module/include/app/io.h
Normal file
35
Modules/app-module/include/app/io.h
Normal 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
|
||||
@ -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.
|
||||
|
||||
115
Modules/app-module/include/app/stream.h
Normal file
115
Modules/app-module/include/app/stream.h
Normal 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
|
||||
98
Modules/app-module/private/app/private/fd_table.h
Normal file
98
Modules/app-module/private/app/private/fd_table.h
Normal 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
|
||||
@ -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 {
|
||||
|
||||
18
Modules/app-module/private/app/private/null_device.h
Normal file
18
Modules/app-module/private/app/private/null_device.h
Normal 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
|
||||
15
Modules/app-module/private/app/private/stream_internal.h
Normal file
15
Modules/app-module/private/app/private/stream_internal.h
Normal 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
|
||||
151
Modules/app-module/source/fd_table.cpp
Normal file
151
Modules/app-module/source/fd_table.cpp
Normal 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"
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
106
Modules/app-module/source/io.cpp
Normal file
106
Modules/app-module/source/io.cpp
Normal 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"
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
45
Modules/app-module/source/null_device.cpp
Normal file
45
Modules/app-module/source/null_device.cpp
Normal 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"
|
||||
@ -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);
|
||||
|
||||
|
||||
313
Modules/app-module/source/stream.cpp
Normal file
313
Modules/app-module/source/stream.cpp
Normal 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, ¤t) && 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"
|
||||
294
Modules/app-module/tests/source/io_test.cpp
Normal file
294
Modules/app-module/tests/source/io_test.cpp
Normal 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");
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
158
Modules/app-module/tests/source/stream_test.cpp
Normal file
158
Modules/app-module/tests/source/stream_test.cpp
Normal 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");
|
||||
}
|
||||
@ -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
|
||||
|
||||
24
Tactility/Source/AppStdioWrap.cpp
Normal file
24
Tactility/Source/AppStdioWrap.cpp
Normal 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
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user