Files
esp-idf/examples/system/function_tracing/README.md

139 lines
8.4 KiB
Markdown

| 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-functions` to 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:
```cmake
# 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:
```cmake
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)
1. Connect a JTAG interface and [run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
2. 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
```
3. Start tracing automatically from GDB using the provided `gdbinit` (it breaks at `app_main` and runs `mon esp sysview_mcore start`):
```
riscv32-esp-elf-gdb -x gdbinit build/function_tracing.elf
```
Replace 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, use `mon esp sysview start <core0_file> [core1_file]` instead to write a separate file per core.
4. 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](#instrumenting-your-code)).
For the full feature description and option reference, see the *Compiler-Instrumented Function Tracing* section of the [Application Level Tracing guide](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#compiler-instrumented-function-tracing).
## 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.