Skip to content

Commit 4c6c170

Browse files
committed
Merge branch 'staging/add_wrap_example' into 'master'
example: add an example to show how to wrap functions in IDF and bootloader See merge request espressif/esp-idf!20005
2 parents 8880d38 + 6dfac0d commit 4c6c170

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed

docs/en/api-guides/build-system.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,21 @@ Take care when adding configuration values in this file, as they will be include
654654

655655
``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".
656656

657+
Wrappers to redefine or extend existing functions
658+
-------------------------------------------------
659+
660+
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:
661+
662+
.. code-block:: cmake
663+
664+
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=function_to_redefine")
665+
666+
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.
667+
668+
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.
669+
670+
This mechanism is shown in the example :example:`build_system/wrappers`. Check its ``README.md`` for more details.
671+
657672
.. _config_only_component:
658673

659674
Configuration-Only Components
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# The following lines of boilerplate have to be in your project's
2+
# CMakeLists in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.16)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
project(wrappers)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-S2 | ESP32-S3 |
2+
| ----------------- | ----- | -------- | -------- | -------- | -------- |
3+
4+
# Using wrapper to redefine IDF functions
5+
6+
This examples shows a linker feature that will let anyone redefine or override any public function included in both ESP-IDF and the bootloader.
7+
8+
Thanks to this, it is possible to modify the default behavior of a function or extend it.
9+
10+
## Building this example
11+
12+
To build this example, set the target first:
13+
```
14+
# Build for ESP32-C3 for example
15+
idf.py set-target esp32c3
16+
```
17+
18+
Launch the build:
19+
```
20+
idf.py build
21+
```
22+
23+
Finally, flash it and check the monitor:
24+
```
25+
idf.py flash monitor
26+
```
27+
28+
## Expected output
29+
30+
This example will redefine the bootloader's `bootloader_print_banner` function and IDF's `esp_restart` function.
31+
32+
Thus in the monitor, the following messages should appear:
33+
```
34+
I (30) boot-wrapper: message from a bootloader wrapper
35+
[...]
36+
Restarting in 5 seconds...
37+
Restarting is progress...
38+
[...]
39+
```
40+
41+
This shows that functions have been redefined successfully.
42+
43+
## How does it work?
44+
45+
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.
46+
47+
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.
48+
49+
## Limitations
50+
51+
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.
52+
53+
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.
54+
55+
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.
56+
57+
Overall, this wrapping method should be used at your own risk.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Since the IDF bootloader is part of a project different from the application one, we cannot put bootloader wrappers
2+
# inside the application. Thus, we need to create this bootloader component, which will be included inside the
3+
# final bootloader binary, to store our wrappers.
4+
idf_component_register(SRCS "wrapper.c"
5+
# Since our source file doesn't contain any symbol strictly required by the linker, the latter
6+
# may completely omit it and discard out wrapper. Thus, the following option will force it to
7+
# include our object file inside the final binary.
8+
WHOLE_ARCHIVE)
9+
10+
# Tell the linker that we want to redefine the function named `bootloader_print_banner`.
11+
# We must now define a function named __wrap_bootloader_print_banner, which has the same
12+
# signature as the former implementation.
13+
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=bootloader_print_banner")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: CC0-1.0
5+
*/
6+
#include "esp_log.h"
7+
8+
static const char *TAG = "boot-wrapper";
9+
10+
/**
11+
* Declare the following symbol in order to have access to the original function implementation
12+
*/
13+
extern void __real_bootloader_print_banner(void);
14+
15+
/**
16+
* Extend the bootloader's print banner function.
17+
*/
18+
void __wrap_bootloader_print_banner(void)
19+
{
20+
/* Let's first let the original code run */
21+
__real_bootloader_print_banner();
22+
/* and then extend it by printing another message */
23+
ESP_LOGI(TAG, "message from a bootloader wrapper");
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# For this component, contraty to the bootloader's one, we don't need the WHOLE_ARCHIVE option.
2+
# This is due to the fact that this source file, app_wrapper.c, contains a strong symbol that is required by the linker
3+
# in order to generate the final binary: `app_main`. Thus, the whole app_wrapper object file, including the wrapper
4+
# it contains, will be included inside the application binary.
5+
idf_component_register(SRCS "app_wrapper.c")
6+
7+
# Tell the linker that we want to redefine the function named `esp_restart`.
8+
# We must now define a function named __wrap_esp_restart, which has the same signature as the former implementation.
9+
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=esp_restart")
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: CC0-1.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include "sdkconfig.h"
9+
#include "freertos/FreeRTOS.h"
10+
#include "freertos/task.h"
11+
12+
/**
13+
* Declare the symbol pointing to the former implementation of esp_restart function
14+
*/
15+
extern void __real_esp_restart(void);
16+
17+
18+
/**
19+
* Redefine esp_restart function to print a message before actually restarting
20+
*/
21+
void __wrap_esp_restart(void)
22+
{
23+
printf("Restarting in progress...\n");
24+
/* Call the former implementation to actually restart the board */
25+
__real_esp_restart();
26+
}
27+
28+
void app_main(void)
29+
{
30+
printf("Restarting in 5 seconds...\n");
31+
vTaskDelay(5000 / portTICK_PERIOD_MS);
32+
esp_restart();
33+
}

0 commit comments

Comments
 (0)