mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
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:
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user