| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|---|
Example: Compiler-Instrumented Function Tracing (function_tracing)
This example shows how to trace function entry/exit automatically with the esp_trace compiler-instrumented function tracing feature, and how to control which code is instrumented.
When a source file is built with the GCC flag -finstrument-functions, the compiler inserts a call at the start and end of every function. Those calls go to hooks provided by esp_trace, which forward the events to the active trace encoder (SystemView here). The events carry the raw function and call-site addresses. SystemView shows the enter/exit sequence with those addresses. Resolving the addresses to function names is done separately against the ELF file (for example with addr2line or a custom tool).
Unlike RTOS-aware tracing (task switches, semaphores, etc.), function tracing needs no manual trace points. You add one compile flag to a component and the whole call flow is captured.
What this example demonstrates
- Instrument a whole component (
components/ft_demo) by adding-finstrument-functionsto its build. - Instrument only selected sources (
main) by setting the flag per source file. -finstrument-functions-exclude-file-list— exclude whole files as a comma-separated list (ft_demo_hot.c,ft_demo_quiet.c).-finstrument-functions-exclude-function-list— exclude a single function by name (ft_demo_secret).
The workload (example_workload()) calls four things every iteration:
| Call | Instrumented? | Why |
|---|---|---|
ft_demo_run() |
yes | component is instrumented, appears as enter/exit |
ft_demo_secret() |
no | excluded by -...-exclude-function-list |
ft_demo_hot_loop() |
no | its file is excluded by -...-exclude-file-list |
ft_demo_quiet_path() |
no | its file is the 2nd -...-exclude-file-list entry |
So in the captured trace you should see ft_demo_run -> ft_demo_level1 -> ft_demo_level2, but not ft_demo_secret, ft_demo_hot_loop or ft_demo_quiet_path.
The same workload task is created pinned to each core, so on a dual-core target the multi-core capture shows function tracing on both cores (one task per core). On a single-core target it is one task on core 0.
Project layout
function_tracing/
├── main/
│ ├── function_tracing_example_main.c # workload + trace setup
│ └── CMakeLists.txt # instruments this source only
├── components/
│ └── ft_demo/
│ ├── ft_demo.c # instrumented call graph + excluded-by-name function
│ ├── ft_demo_hot.c # excluded-by-file (1st exclude-file-list entry)
│ ├── ft_demo_quiet.c # excluded-by-file (2nd exclude-file-list entry)
│ └── CMakeLists.txt # instruments the whole component + exclude flags
├── sdkconfig.defaults # enables function tracing
├── sdkconfig.ci.jtag # apptrace over JTAG
├── SYSVIEW_FreeRTOS.txt # event names for the function-trace module
└── gdbinit
Instrumenting your code
A component is instrumented from its own CMakeLists.txt, after idf_component_register(), by adding -finstrument-functions to its build. Guard it with the config so the flag is absent when the feature is off — otherwise the __cyg_profile_* hooks are undefined at link time:
# whole component (see components/ft_demo/CMakeLists.txt)
if(CONFIG_ESP_TRACE_FUNCTION_TRACE)
target_compile_options(${COMPONENT_LIB} PRIVATE -finstrument-functions)
endif()
# or only specific sources (see main/CMakeLists.txt)
if(CONFIG_ESP_TRACE_FUNCTION_TRACE)
set_source_files_properties(my_file.c PROPERTIES
COMPILE_OPTIONS "-finstrument-functions")
endif()
Instrumentation is applied per component, so you trace only your own code. ESP-IDF internals are not instrumented (this keeps event volume manageable and avoids tracing code that runs with the flash cache disabled).
To exclude individual files or functions from an otherwise-instrumented component, add the GCC blocklist flags:
if(CONFIG_ESP_TRACE_FUNCTION_TRACE)
target_compile_options(${COMPONENT_LIB} PRIVATE
-finstrument-functions
-finstrument-functions-exclude-file-list=ft_demo_hot,ft_demo_quiet
-finstrument-functions-exclude-function-list=ft_demo_secret)
endif()
Each list is a comma-separated set of substrings matched against the source file path / function name, so a single entry can cover several files and you can list several at once.
How recording starts
This example records when the host starts the SystemView session over JTAG (OpenOCD mon esp sysview_mcore start). Because CONFIG_ESP_TRACE_FUNCTION_TRACE_AUTO_START is enabled, no explicit esp_trace_function_trace_start() call is needed. Recording follows the encoder's recording state.
Build, run and capture (JTAG + OpenOCD)
-
Connect a JTAG interface and run OpenOCD.
-
Build and flash with the JTAG config:
idf.py set-target <target> idf.py -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.ci.jtag" build flash monitor -
Start tracing automatically from GDB using the provided
gdbinit(it breaks atapp_mainand runsmon esp sysview_mcore start):riscv32-esp-elf-gdb -x gdbinit build/function_tracing.elfReplace the GDB binary with the one matching your target (e.g.
xtensa-esp32-elf-gdb). Trace data is written to/tmp/function_tracing.svdat.This example uses
esp sysview_mcore, which captures all cores into a single file in SEGGER's official multi-core format. It requires SystemView v3.60 or later. On a dual-core target the one file holds both cores. For older SystemView versions, usemon esp sysview start <core0_file> [core1_file]instead to write a separate file per core. -
When enough data is captured, stop tracing:
mon esp sysview_mcore stop
Viewing in SystemView and naming the events
Open the .svdat file in the SEGGER SystemView application.
The function-trace events arrive as numeric module event IDs. To show them as names, copy this example's SYSVIEW_FreeRTOS.txt into your SystemView installation directory (or merge its entries):
512 function_enter func=%p call_site=%p
513 function_exit func=%p call_site=%p
func is the traced function start address. call_site is the caller return address. SystemView records both as raw addresses. Map them to function names offline against the ELF file (for example with addr2line).
The IDs are not chosen by the application: SystemView assigns each registered module an EventOffset (the first module gets 512) and events are EventOffset + index. In this example the function-trace module is the only one registered, so it occupies 512-513.
Configuration
Function tracing is configured under Component config → ESP Trace Configuration → Function Tracing in menuconfig. Each option has built-in help. This example enables it and relies on the default CONFIG_ESP_TRACE_FUNCTION_TRACE_AUTO_START so recording follows the host session. File/function exclusion uses the -finstrument-functions-exclude-* compile flags (see Instrumenting your code).
For the full feature description and option reference, see the Compiler-Instrumented Function Tracing section of the Application Level Tracing guide.
Limitations
- Instrumentation adds overhead to every traced function call and increases code size and stack usage.
- Do not instrument code that runs with the flash cache disabled (IRAM ISRs, SPI flash operations). Keep instrumentation scoped to your own components.