example: add an example to show how to wrap functions in IDF and bootloader

A section in the documentation has also been added to talk about the wrap feature of the linker.
This commit is contained in:
Omar Chebib 2022-09-07 14:20:00 +08:00
parent 96d03461ae
commit 6dfac0dd68
7 changed files with 157 additions and 0 deletions

View File

@ -655,6 +655,21 @@ Take care when adding configuration values in this file, as they will be include
``project_include.cmake`` files are used inside ESP-IDF, for defining project-wide build features such as ``esptool.py`` command line arguments and the ``bootloader`` "special app".
Wrappers to redefine or extend existing functions
-------------------------------------------------
Thanks to the linker's wrap feature, it is possible to redefine or extend the behavior of an existing ESP-IDF function. To do so, you will need to provide the following CMake declaration in your project's ``CMakeLists.txt`` file:
.. code-block:: cmake
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=function_to_redefine")
Where ``function_to_redefine`` is the name of the function to redefine or extend. This option will let the linker replace all the calls to ``function_to_redefine`` functions in the binary libraries be changed to calls to ``__wrap_function_to_redefine`` function. Thus, you must define this new symbol in your application.
The linker will provide a new symbol named ``__real_function_to_redefine`` which points to the former implementation of the function to redefine. It can be called from the new implementation, making it an extension of the former one.
This mechanism is shown in the example :example:`build_system/wrappers`. Check its ``README.md`` for more details.
.. _config_only_component:
Configuration-Only Components

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(wrappers)

View File

@ -0,0 +1,57 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
# Using wrapper to redefine IDF functions
This examples shows a linker feature that will let anyone redefine or override any public function included in both ESP-IDF and the bootloader.
Thanks to this, it is possible to modify the default behavior of a function or extend it.
## Building this example
To build this example, set the target first:
```
# Build for ESP32-C3 for example
idf.py set-target esp32c3
```
Launch the build:
```
idf.py build
```
Finally, flash it and check the monitor:
```
idf.py flash monitor
```
## Expected output
This example will redefine the bootloader's `bootloader_print_banner` function and IDF's `esp_restart` function.
Thus in the monitor, the following messages should appear:
```
I (30) boot-wrapper: message from a bootloader wrapper
[...]
Restarting in 5 seconds...
Restarting is progress...
[...]
```
This shows that functions have been redefined successfully.
## How does it work?
When redefining a function, the option `-Wl,--wrap=a_common_idf_function` will tell the linker to replace all the occurrences of `a_common_idf_function` function calls in the code and precompiled libraries to `__wrap_a_common_idf_function`. Thus, the application should now provide such symbol.
Moreover, the linker will also provide a new symbol, `__real_a_common_idf_function`, which points to the former function implementation. This is very handy if the new redefinition needs to call the former implementation at some point.
## Limitations
Because the wrapping system happens at link time, the function to redefine must be global. Indeed, functions marked as `static` won't be visible by the linker and thus, cannot be replaced.
Moreover, even though it is not recommended, it is also possible to wrap IDF internal functions that are not marked as `static`. However, keep in mind that such function may be renamed or removed from one IDF version to another. Therefore, after upgrading IDF, make sure the functions you redefine have not been renamed or removed.
Finally, wrapping certain functions may lead to bugs or undefined behavior, for example, redefining a function in IRAM by a function in flash may lead to exceptions at runtime.
Overall, this wrapping method should be used at your own risk.

View File

@ -0,0 +1,13 @@
# Since the IDF bootloader is part of a project different from the application one, we cannot put bootloader wrappers
# inside the application. Thus, we need to create this bootloader component, which will be included inside the
# final bootloader binary, to store our wrappers.
idf_component_register(SRCS "wrapper.c"
# Since our source file doesn't contain any symbol strictly required by the linker, the latter
# may completely omit it and discard out wrapper. Thus, the following option will force it to
# include our object file inside the final binary.
WHOLE_ARCHIVE)
# Tell the linker that we want to redefine the function named `bootloader_print_banner`.
# We must now define a function named __wrap_bootloader_print_banner, which has the same
# signature as the former implementation.
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=bootloader_print_banner")

View File

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "esp_log.h"
static const char *TAG = "boot-wrapper";
/**
* Declare the following symbol in order to have access to the original function implementation
*/
extern void __real_bootloader_print_banner(void);
/**
* Extend the bootloader's print banner function.
*/
void __wrap_bootloader_print_banner(void)
{
/* Let's first let the original code run */
__real_bootloader_print_banner();
/* and then extend it by printing another message */
ESP_LOGI(TAG, "message from a bootloader wrapper");
}

View File

@ -0,0 +1,9 @@
# For this component, contraty to the bootloader's one, we don't need the WHOLE_ARCHIVE option.
# This is due to the fact that this source file, app_wrapper.c, contains a strong symbol that is required by the linker
# in order to generate the final binary: `app_main`. Thus, the whole app_wrapper object file, including the wrapper
# it contains, will be included inside the application binary.
idf_component_register(SRCS "app_wrapper.c")
# Tell the linker that we want to redefine the function named `esp_restart`.
# We must now define a function named __wrap_esp_restart, which has the same signature as the former implementation.
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_restart")

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
/**
* Declare the symbol pointing to the former implementation of esp_restart function
*/
extern void __real_esp_restart(void);
/**
* Redefine esp_restart function to print a message before actually restarting
*/
void __wrap_esp_restart(void)
{
printf("Restarting in progress...\n");
/* Call the former implementation to actually restart the board */
__real_esp_restart();
}
void app_main(void)
{
printf("Restarting in 5 seconds...\n");
vTaskDelay(5000 / portTICK_PERIOD_MS);
esp_restart();
}