Merge branch 'feat/enable_function_tracing' into 'master'

Enable function tracing (-finstrument-functions)

See merge request espressif/esp-idf!49951
This commit is contained in:
Erhan Kurubas
2026-07-16 15:57:42 +02:00
35 changed files with 1499 additions and 276 deletions
-2
View File
@@ -47,7 +47,6 @@ menu "Application Level Tracing"
config APPTRACE_UART_TX_GPIO
int "UART TX on GPIO<num>"
depends on APPTRACE_DEST_UART
range 0 46
default 12
help
This GPIO is used for UART TX pin.
@@ -55,7 +54,6 @@ menu "Application Level Tracing"
config APPTRACE_UART_RX_GPIO
int "UART RX on GPIO<num>"
depends on APPTRACE_DEST_UART
range 0 46
default 13
help
This GPIO is used for UART RX pin.
+5 -1
View File
@@ -19,6 +19,10 @@ if(CONFIG_ESP_TRACE_ENABLE)
if(CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG)
list(APPEND srcs "adapters/transport/adapter_transport_usb_serial_jtag.c")
endif()
if(CONFIG_ESP_TRACE_FUNCTION_TRACE)
list(APPEND srcs "src/function_trace.c")
endif()
endif()
set(includes
@@ -32,7 +36,7 @@ set(priv_requires
"esp_timer"
"esp_system"
)
set(priv_includes "")
set(priv_includes "private_include")
set(requires "app_trace")
idf_component_register(SRCS ${srcs}
+51
View File
@@ -85,6 +85,57 @@ menu "ESP Trace Configuration"
endmenu
menu "Function Tracing"
depends on ESP_TRACE_ENABLE
config ESP_TRACE_FUNCTION_TRACE
bool "Enable compiler-instrumented function tracing"
default n
help
Trace when functions are entered and exited.
When you build a component with the compiler flag
-finstrument-functions, the compiler inserts a call at the start
and end of every function in that component. These calls go to
hooks provided by esp_trace, which forward the events to the
active trace encoder (for example SystemView).
Enabling this option compiles the hook functions and the
function-trace runtime into the build. It does not add
-finstrument-functions to any code, so on its own it produces no
events.
To trace a component or file, add the GCC function instrumentation flag
from that component's CMakeLists.txt. See the Application Level Tracing
guide and examples/system/function_tracing for CMake examples.
Use the GCC -finstrument-functions-exclude-file-list and
-finstrument-functions-exclude-function-list flags to skip
specific files or functions.
config ESP_TRACE_FUNCTION_TRACE_AUTO_START
bool "Start function tracing automatically when the host starts recording"
depends on ESP_TRACE_FUNCTION_TRACE
default y
help
Controls when function tracing begins recording once a trace
session is active.
When enabled, function tracing follows the encoder's recording
state and begins as soon as the host starts the session (for
example OpenOCD "mon esp sysview_mcore start"), without an
application call.
When disabled, function-trace events are dropped until the
application calls esp_trace_function_trace_start(), even if a host
trace session is already recording. esp_trace_function_trace_stop()
stops recording again.
An active trace session (encoder recording) is required in both
cases.
endmenu
choice ESP_TRACE_TIMESTAMP_SOURCE
depends on ESP_TRACE_ENABLE
prompt "Trace timestamp source"
+52 -1
View File
@@ -231,7 +231,7 @@ The `esp_trace` component supports integration of external trace libraries throu
>
> Encoder and transport callbacks invoked from the hot path — `write`, `flush` / `flush_nolock`, `read`, `take_lock` / `give_lock`, `panic_handler` — run from inside FreeRTOS trace hooks (and from ISR context for `traceISR_ENTER` / `traceISR_EXIT`). They are also called while the encoder's lock is held.
>
> Do **not** call FreeRTOS / IDF APIs that themselves trigger trace hooks from these callbacks. Anything that would emit a `trace*()` macro re-enters the tracing path: it can recurse into your own encoder, deadlock on the encoder's non-recursive spinlock, or call a task-only API from ISR context.
> Do **not** call FreeRTOS / IDF APIs that themselves trigger trace hooks from these callbacks. Anything that would invoke a `trace*()` macro re-enters the tracing path: it can recurse into your own encoder, deadlock on the encoder's non-recursive spinlock, or call a task-only API from ISR context.
>
> Specifically avoid:
> - Task APIs: `vTaskDelay`, `vTaskSuspend`, `xTaskNotify*`, anything that yields.
@@ -411,6 +411,57 @@ target_link_libraries(${esp_trace_lib} INTERFACE $<TARGET_NAME_IF_EXISTS:${COMPO
**Note:** External trace libraries should use `CONFIG_ESP_TRACE_LIB_EXTERNAL=y` instead of defining their own Kconfig option in the esp_trace menu. This keeps the external component independent from the esp_trace component.
## Function Tracing
`esp_trace` can record function entry/exit using the compiler's `-finstrument-functions` feature. The runtime and hooks live in `esp_trace`. The active encoder (e.g. SystemView) formats the events.
### Enable
```
CONFIG_ESP_TRACE_FUNCTION_TRACE=y
```
### Instrument selected code
Add the flag only to the components or files you want traced:
```cmake
# instrument the whole component
target_compile_options(${COMPONENT_LIB} PRIVATE -finstrument-functions)
```
To narrow it down, exclude files or functions:
```cmake
target_compile_options(${COMPONENT_LIB} PRIVATE
-finstrument-functions
-finstrument-functions-exclude-file-list=foo.c,bar.c
-finstrument-functions-exclude-function-list=hot_fn,isr_handler)
```
Individual functions can also be excluded in source with `__attribute__((no_instrument_function))`.
### Control from the application
```c
#include "esp_trace_function_trace.h"
esp_trace_function_trace_start();
// ...
esp_trace_function_trace_stop();
```
Recording is active only while function tracing is started and the encoder reports it is recording (for SystemView this follows the host connecting and starting a recording over JTAG/UART).
> Do not instrument `esp_trace`, encoders, transports, or SEGGER sources. They must stay free of `-finstrument-functions` to avoid recursion. The target sends addresses, not symbol names. If supported, the host viewer can resolve them from the ELF.
### Limitations
- No early-boot tracing. Events are recorded only after a trace session and the encoder are running.
- The target sends addresses, not symbol strings. The host viewer can optionally resolve them from the ELF.
- Instrumentation adds hook overhead to every traced call and increases code size and stack usage.
- Whole-IDF instrumentation is not recommended. Instrument selected components or files only.
## Documentation
For detailed usage instructions, see:
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* @brief Start recording compiler-instrumented function events.
*
* Resolves the active encoder's function-trace capability once and enables the
* hooks. Requires an active trace session.
*
* @return ESP_OK on success,
* ESP_ERR_INVALID_STATE if no trace session is active,
* ESP_ERR_NOT_SUPPORTED if the encoder lacks function-trace callbacks.
*/
esp_err_t esp_trace_function_trace_start(void);
/**
* @brief Stop recording function events.
*
* @return ESP_OK on success.
*/
esp_err_t esp_trace_function_trace_stop(void);
#ifdef __cplusplus
}
#endif
@@ -11,6 +11,7 @@ extern "C" {
#endif
#include <stddef.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_trace_types.h"
@@ -24,7 +25,7 @@ typedef struct esp_trace_transport esp_trace_transport_t;
* Defines the interface for trace encoders (libraries).
*
* @warning Runtime callbacks (write, flush, take_lock, give_lock, panic_handler)
* must not call FreeRTOS / IDF APIs that themselves emit trace hooks
* must not call FreeRTOS / IDF APIs that themselves trigger trace hooks
* (e.g. vTaskDelay, xQueue*, xSemaphore*) — doing so re-enters the
* tracing path and can deadlock on the encoder lock or crash in ISR
* context.
@@ -82,6 +83,31 @@ typedef struct {
*/
void (*give_lock)(esp_trace_encoder_t *enc, unsigned int_state);
/*
* Optional function-trace callbacks. Leave NULL if unsupported.
*
* Capability contract: an encoder supports function tracing when both
* function_enter and function_exit are set. The runtime checks these once at
* start and returns ESP_ERR_NOT_SUPPORTED otherwise. Callbacks receive raw
* addresses. The backend stays free to choose its own payload encoding.
*/
/**
* @brief Send a function entry event
* @param enc Encoder instance
* @param func Start address of the entered function
* @param call_site Return address in the caller
*/
void (*function_enter)(esp_trace_encoder_t *enc, void *func, void *call_site);
/**
* @brief Send a function exit event
* @param enc Encoder instance
* @param func Start address of the exited function
* @param call_site Return address in the caller
*/
void (*function_exit)(esp_trace_encoder_t *enc, void *func, void *call_site);
} esp_trace_encoder_vtable_t;
/**
@@ -93,6 +119,17 @@ struct esp_trace_encoder {
void *ctx; ///< Encoder specific context
};
/**
* @brief Report a change in the encoder's recording state.
*
* Encoders whose recording is controlled by the host (e.g. SystemView start/stop
* over JTAG or UART) call this so dependent features such as function tracing can
* follow the actual recording state.
*
* @param active true if the encoder is now recording, false otherwise.
*/
void esp_trace_notify_recording_state(bool active);
#ifdef __cplusplus
}
#endif
@@ -35,7 +35,7 @@ typedef enum {
* Defines the interface for trace transports.
*
* @warning Runtime callbacks (read, write, flush_nolock, panic_handler) must
* not call FreeRTOS / IDF APIs that themselves emit trace hooks
* not call FreeRTOS / IDF APIs that themselves trigger trace hooks
* (e.g. vTaskDelay, xQueue*, xSemaphore*) — they are invoked from
* inside the encoder's lock and from ISR context, so re-entering
* the tracing path can deadlock or assert.
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "esp_err.h"
#include "esp_trace_port_encoder.h"
/**
* @brief Returns the active encoder instance, or NULL if no session exists.
*
* @return The active encoder instance, or NULL if no session exists.
*/
esp_trace_encoder_t *esp_trace_get_active_encoder(void);
/**
* @brief Notify the function-trace runtime of the encoder's recording state.
*
* @param active True if the encoder is recording, false otherwise.
*/
void esp_trace_function_trace_notify_recording(bool active);
+25 -2
View File
@@ -22,6 +22,7 @@
#include "esp_trace_registry.h"
#include "esp_trace.h"
#include "esp_trace_port_transport.h"
#include "esp_trace_internal.h"
#include "esp_private/startup_internal.h"
#include "esp_private/esp_sys_event_system_init.h"
#include "esp_private/esp_sys_event_panic.h"
@@ -164,7 +165,11 @@ esp_err_t esp_trace_start(void)
return ESP_ERR_NOT_SUPPORTED;
}
return h->encoder.vt->start(&h->encoder);
esp_err_t err = h->encoder.vt->start(&h->encoder);
if (err == ESP_OK) {
esp_trace_notify_recording_state(true);
}
return err;
}
esp_err_t esp_trace_stop(void)
@@ -178,7 +183,11 @@ esp_err_t esp_trace_stop(void)
return ESP_ERR_NOT_SUPPORTED;
}
return h->encoder.vt->stop(&h->encoder);
esp_err_t err = h->encoder.vt->stop(&h->encoder);
if (err == ESP_OK) {
esp_trace_notify_recording_state(false);
}
return err;
}
esp_err_t esp_trace_flush(void)
@@ -218,6 +227,20 @@ esp_trace_handle_t esp_trace_get_active_handle(void)
return s_active_handle;
}
esp_trace_encoder_t *esp_trace_get_active_encoder(void)
{
return s_active_handle ? &s_active_handle->encoder : NULL;
}
void esp_trace_notify_recording_state(bool active)
{
#if CONFIG_ESP_TRACE_FUNCTION_TRACE
esp_trace_function_trace_notify_recording(active);
#else
(void)active;
#endif
}
void esp_trace_panic_handler(const void *info)
{
esp_trace_handle_t h = s_active_handle;
+143
View File
@@ -0,0 +1,143 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Compiler-instrumented function tracing runtime.
*
* When a component is built with -finstrument-functions, the compiler inserts a
* call to __cyg_profile_func_enter/exit at the start and end of every function.
* These hooks gate the events and forward them to the active trace encoder.
*/
#include <stdbool.h>
#include <stdint.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_cpu.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "esp_trace_port_encoder.h"
#include "esp_trace_function_trace.h"
#include "esp_trace_internal.h"
#define NO_INSTRUMENT __attribute__((no_instrument_function))
static volatile bool s_hook_active;
#if CONFIG_ESP_TRACE_FUNCTION_TRACE_AUTO_START
/* Auto start: function tracing follows the encoder recording state. */
static bool s_app_enabled = true;
#else
/* Manual start: enabled only when the application calls esp_trace_function_trace_start(). */
static bool s_app_enabled;
#endif
/* Encoder recording state. Defaults true for encoders that do not report it. */
static bool s_encoder_recording = true;
/* Per-core re-entry guard so an interrupt cannot run the hooks recursively. */
static volatile bool s_in_hook[SOC_CPU_CORES_NUM];
static esp_trace_encoder_t *s_enc;
static void (*s_fn_enter)(esp_trace_encoder_t *enc, void *func, void *call_site);
static void (*s_fn_exit)(esp_trace_encoder_t *enc, void *func, void *call_site);
static esp_err_t resolve_encoder(void)
{
if (s_enc) {
return ESP_OK;
}
esp_trace_encoder_t *enc = esp_trace_get_active_encoder();
if (!enc || !enc->vt) {
return ESP_ERR_INVALID_STATE;
}
const esp_trace_encoder_vtable_t *vt = enc->vt;
if (!vt->function_enter || !vt->function_exit) {
return ESP_ERR_NOT_SUPPORTED;
}
s_enc = enc;
s_fn_enter = vt->function_enter;
s_fn_exit = vt->function_exit;
return ESP_OK;
}
static void update_hook_state(void)
{
s_hook_active = s_app_enabled && s_encoder_recording && (s_enc != NULL);
}
void esp_trace_function_trace_notify_recording(bool active)
{
s_encoder_recording = active;
#if CONFIG_ESP_TRACE_FUNCTION_TRACE_AUTO_START
if (active) {
(void)resolve_encoder();
}
#endif
update_hook_state();
}
esp_err_t esp_trace_function_trace_start(void)
{
esp_err_t err = resolve_encoder();
if (err != ESP_OK) {
return err;
}
s_app_enabled = true;
update_hook_state();
return ESP_OK;
}
esp_err_t esp_trace_function_trace_stop(void)
{
s_app_enabled = false;
update_hook_state();
return ESP_OK;
}
static inline NO_INSTRUMENT bool hook_acquire(void)
{
UBaseType_t irq = portSET_INTERRUPT_MASK_FROM_ISR();
bool acquired = false;
int core = esp_cpu_get_core_id();
if (!s_in_hook[core]) {
s_in_hook[core] = true;
acquired = true;
}
portCLEAR_INTERRUPT_MASK_FROM_ISR(irq);
return acquired;
}
static inline NO_INSTRUMENT void hook_release(void)
{
s_in_hook[esp_cpu_get_core_id()] = false;
}
NO_INSTRUMENT void __cyg_profile_func_enter(void *func, void *call_site)
{
if (!s_hook_active) {
return;
}
if (!hook_acquire()) {
return;
}
s_fn_enter(s_enc, func, call_site);
hook_release();
}
NO_INSTRUMENT void __cyg_profile_func_exit(void *func, void *call_site)
{
if (!s_hook_active) {
return;
}
if (!hook_acquire()) {
return;
}
s_fn_exit(s_enc, func, call_site);
hook_release();
}