mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
Merge branch 'feat/enable_function_tracing_v6.0' into 'release/v6.0'
Enable function tracing (-finstrument-functions) (v6.0) See merge request espressif/esp-idf!52656
This commit is contained in:
@@ -27,6 +27,18 @@ examples/system/tracing/esp_trace_custom_library:
|
||||
- esp_trace
|
||||
- freertos
|
||||
|
||||
examples/system/tracing/function_tracing:
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: IDF_TARGET == "esp32h4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
depends_components:
|
||||
- esp_trace
|
||||
- app_trace
|
||||
|
||||
examples/system/tracing/gcov:
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(function_tracing)
|
||||
@@ -0,0 +1,138 @@
|
||||
| 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 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# 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 guide](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/tracing/function-tracing.html).
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,2 @@
|
||||
512 function_enter func=%p call_site=%p
|
||||
513 function_exit func=%p call_site=%p
|
||||
@@ -0,0 +1,15 @@
|
||||
idf_component_register(SRCS "ft_demo.c" "ft_demo_hot.c" "ft_demo_quiet.c"
|
||||
INCLUDE_DIRS "include")
|
||||
|
||||
# Opt the whole component into compiler-instrumented function tracing by adding
|
||||
# -finstrument-functions to its build. The guard keeps the flag out when the
|
||||
# feature is off, otherwise the __cyg_profile_* hooks would be undefined at link.
|
||||
# The exclude-list flags skip specific files / functions:
|
||||
# - ft_demo_hot.c and ft_demo_quiet.c are excluded by file (comma-separated)
|
||||
# - ft_demo_secret is excluded by function name
|
||||
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()
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Function Tracing Example - demo library
|
||||
*/
|
||||
|
||||
#include "ft_demo.h"
|
||||
|
||||
/* noinline keeps each function as a distinct call so the compiler inserts
|
||||
* enter/exit hooks instead of inlining them away. */
|
||||
static uint32_t __attribute__((noinline)) ft_demo_level2(uint32_t v)
|
||||
{
|
||||
return v * 3u + 1u;
|
||||
}
|
||||
|
||||
static uint32_t __attribute__((noinline)) ft_demo_level1(uint32_t v)
|
||||
{
|
||||
return ft_demo_level2(v) + ft_demo_level2(v + 1u);
|
||||
}
|
||||
|
||||
void ft_demo_run(uint32_t iteration)
|
||||
{
|
||||
volatile uint32_t r = ft_demo_level1(iteration);
|
||||
(void)r;
|
||||
}
|
||||
|
||||
void __attribute__((noinline)) ft_demo_secret(void)
|
||||
{
|
||||
/* Excluded from instrumentation by name, so no enter/exit is recorded even
|
||||
* though this file is compiled with -finstrument-functions. */
|
||||
__asm__ volatile("");
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Function Tracing Example - demo library (excluded file)
|
||||
*/
|
||||
|
||||
#include "ft_demo.h"
|
||||
|
||||
/* This whole file is listed in the component's -finstrument-functions-exclude-file-list
|
||||
* flag, so none of its functions are instrumented even though the component opts
|
||||
* in. Use this for hot paths you do not want to trace. */
|
||||
uint32_t ft_demo_hot_loop(uint32_t n)
|
||||
{
|
||||
uint32_t acc = 0;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
acc += i * i;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Function Tracing Example - demo library (second excluded file)
|
||||
*/
|
||||
|
||||
#include "ft_demo.h"
|
||||
|
||||
/* A second file added to the component's -finstrument-functions-exclude-file-list
|
||||
* as a separate, comma-separated entry. It shows the exclude list accepts more
|
||||
* than one substring. Like ft_demo_hot.c, none of its functions are instrumented. */
|
||||
uint32_t ft_demo_quiet_path(uint32_t n)
|
||||
{
|
||||
uint32_t acc = 1;
|
||||
for (uint32_t i = 1; i <= n; i++) {
|
||||
acc = (acc * i) % 1000u;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Function Tracing Example - demo library
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Runs a small nested call graph (ft_demo_run -> level1 -> level2). These
|
||||
* functions are instrumented and appear as function_enter/function_exit. */
|
||||
void ft_demo_run(uint32_t iteration);
|
||||
|
||||
/* Instrumented file, but excluded by name through the component's
|
||||
* -finstrument-functions-exclude-function-list flag: it produces no events. */
|
||||
void ft_demo_secret(void);
|
||||
|
||||
/* Lives in ft_demo_hot.c, which is excluded as a whole through the component's
|
||||
* -finstrument-functions-exclude-file-list flag: it produces no events. */
|
||||
uint32_t ft_demo_hot_loop(uint32_t n);
|
||||
|
||||
/* Lives in ft_demo_quiet.c, a second comma-separated entry in the component's
|
||||
* -finstrument-functions-exclude-file-list flag: it also produces no events. */
|
||||
uint32_t ft_demo_quiet_path(uint32_t n);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,13 @@
|
||||
set pagination off
|
||||
target remote :3333
|
||||
|
||||
mon reset halt
|
||||
maintenance flush register-cache
|
||||
|
||||
b app_main
|
||||
commands
|
||||
mon esp sysview_mcore start file:///tmp/function_tracing.svdat
|
||||
c
|
||||
end
|
||||
|
||||
c
|
||||
@@ -0,0 +1,9 @@
|
||||
idf_component_register(SRCS "function_tracing_example_main.c"
|
||||
PRIV_REQUIRES ft_demo
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
# Demonstrate file-level instrumentation. It instruments only this source.
|
||||
if(CONFIG_ESP_TRACE_FUNCTION_TRACE)
|
||||
set_source_files_properties(function_tracing_example_main.c PROPERTIES
|
||||
COMPILE_OPTIONS "-finstrument-functions")
|
||||
endif()
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Function Tracing Example
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_trace.h"
|
||||
#include "ft_demo.h"
|
||||
|
||||
static const char *TAG = "function-tracing";
|
||||
|
||||
static void __attribute__((noinline)) example_workload(uint32_t iteration)
|
||||
{
|
||||
ft_demo_run(iteration); /* traced: ft_demo_run -> level1 -> level2 */
|
||||
ft_demo_secret(); /* excluded by function name: not traced */
|
||||
ft_demo_hot_loop(64); /* excluded by file: not traced */
|
||||
ft_demo_quiet_path(8); /* excluded by file (2nd list entry): not traced */
|
||||
}
|
||||
|
||||
static void example_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
uint32_t iteration = 0;
|
||||
|
||||
while (1) {
|
||||
example_workload(iteration);
|
||||
ESP_LOGI(TAG, "workload iteration %" PRIu32 " on core %d", iteration, xPortGetCoreID());
|
||||
iteration++;
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_TRACE_TRANSPORT_APPTRACE
|
||||
#include "esp_app_trace.h"
|
||||
esp_trace_open_params_t esp_trace_get_user_params(void)
|
||||
{
|
||||
static esp_apptrace_config_t app_trace_config = APPTRACE_CONFIG_DEFAULT();
|
||||
|
||||
esp_trace_open_params_t trace_params = {
|
||||
.core_cfg = NULL,
|
||||
.encoder_name = "sysview",
|
||||
.encoder_cfg = NULL,
|
||||
.transport_name = "apptrace",
|
||||
.transport_cfg = &app_trace_config,
|
||||
};
|
||||
return trace_params;
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Hello from function_tracing example!");
|
||||
|
||||
/* Recording is host-driven: it starts when the SystemView host (OpenOCD
|
||||
* "mon esp sysview_mcore start") begins the session. No explicit start call
|
||||
* is needed because CONFIG_ESP_TRACE_FUNCTION_TRACE_AUTO_START is enabled. */
|
||||
|
||||
|
||||
for (int core = 0; core < CONFIG_FREERTOS_NUMBER_OF_CORES; core++) {
|
||||
char name[configMAX_TASK_NAME_LEN];
|
||||
snprintf(name, sizeof(name), "ft_workload%d", core);
|
||||
xTaskCreatePinnedToCore(example_task, name, 4096, NULL, 5, NULL, core);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=6.0'
|
||||
espressif/esp_sysview: ^1
|
||||
@@ -0,0 +1,156 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import json
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import typing
|
||||
|
||||
import pexpect
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
# Function-trace events are a SystemView module. The first (only) registered module
|
||||
# gets EventOffset 512, so enter uses ID 512 and exit uses ID 513.
|
||||
FT_EVENT_ENTER = 512
|
||||
FT_EVENT_EXIT = 513
|
||||
|
||||
# Demo source compiled with instrumentation. Its functions must be traced.
|
||||
INSTRUMENTED_SOURCE = 'ft_demo.c'
|
||||
# Demo sources excluded by file. Their functions must never be traced.
|
||||
EXCLUDED_SOURCES = ['ft_demo_hot.c', 'ft_demo_quiet.c']
|
||||
|
||||
|
||||
def _encode_event_id(event_id: int) -> bytes:
|
||||
"""Encode a SystemView event ID the way SEGGER_SYSVIEW does (base-128, LSB first)."""
|
||||
out = bytearray()
|
||||
while True:
|
||||
b = event_id & 0x7F
|
||||
event_id >>= 7
|
||||
out.append(b | 0x80 if event_id else b)
|
||||
if not event_id:
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def _toolchain_prefix(binary_path: str) -> str:
|
||||
with open(os.path.join(binary_path, 'project_description.json')) as f:
|
||||
return str(json.load(f)['monitor_toolprefix'])
|
||||
|
||||
|
||||
def _validate_function_trace_manual(trace_log: str) -> None:
|
||||
"""Fallback validation when no toolchain is available to decode the capture.
|
||||
|
||||
The module description string is not in a JTAG capture (it is recorded before
|
||||
recording is enabled and never re-sent), so check for the enter/exit event IDs.
|
||||
"""
|
||||
with open(trace_log, 'rb') as f:
|
||||
content = f.read()
|
||||
enter = content.count(_encode_event_id(FT_EVENT_ENTER))
|
||||
exit_ = content.count(_encode_event_id(FT_EVENT_EXIT))
|
||||
assert enter > 0 and exit_ > 0, f'no function enter/exit events in {trace_log} (enter={enter}, exit={exit_})'
|
||||
|
||||
|
||||
def _validate_function_trace(trace_log: str, idf_path: str, elf_file: str, binary_path: str) -> None:
|
||||
"""Decode function-trace events with sysviewtrace_proc.py and check instrumented
|
||||
functions are traced while excluded sources are not. Fall back to counting
|
||||
enter/exit packets when the target toolchain is not available."""
|
||||
toolchain = _toolchain_prefix(binary_path)
|
||||
if shutil.which(f'{toolchain}addr2line') is None:
|
||||
print(f'addr2line not found for {toolchain}, using manual validation')
|
||||
_validate_function_trace_manual(trace_log)
|
||||
return
|
||||
|
||||
proc_script = os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py')
|
||||
result = subprocess.run(
|
||||
[sys.executable, proc_script, '-b', elf_file, '-t', toolchain, '-i', 'func', f'file://{trace_log}'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
report = result.stdout + result.stderr
|
||||
assert result.returncode == 0, f'sysviewtrace_proc.py failed:\n{report}'
|
||||
|
||||
print(f'{report}')
|
||||
|
||||
m = re.search(r'Processed (\d+) function trace events\.', report)
|
||||
assert m and int(m.group(1)) > 0, f'no function trace events decoded:\n{report}'
|
||||
assert INSTRUMENTED_SOURCE in report, f'instrumented functions ({INSTRUMENTED_SOURCE}) not traced:\n{report}'
|
||||
for excluded in EXCLUDED_SOURCES:
|
||||
assert excluded not in report, f'excluded source {excluded} was traced:\n{report}'
|
||||
|
||||
|
||||
def _validate_trace_data(trace_log: str, target: str, dual_core: bool) -> None:
|
||||
"""Validate the multi-core capture contains SystemView trace data for each core."""
|
||||
with open(trace_log, 'rb') as f:
|
||||
content = f.read()
|
||||
for idx in range(2 if dual_core else 1):
|
||||
search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode()
|
||||
assert search_str in content, f'SysView core{idx} trace data not found in {trace_log}'
|
||||
|
||||
|
||||
def _test_function_tracing_jtag(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
# Single multi-core capture file (esp sysview_mcore).
|
||||
trace_log = os.path.join(dut.logdir, 'function_tracing.svdat')
|
||||
dual_core = not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3'
|
||||
|
||||
# Prepare gdbinit file pointing at this run's capture file
|
||||
gdb_logfile = os.path.join(dut.logdir, 'gdb.txt')
|
||||
gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit')
|
||||
gdbinit = os.path.join(dut.logdir, 'gdbinit')
|
||||
with open(gdbinit_orig) as f_r, open(gdbinit, 'w') as f_w:
|
||||
for line in f_r:
|
||||
if line.startswith('mon esp sysview_mcore start'):
|
||||
f_w.write(f'mon esp sysview_mcore start file://{trace_log}\n')
|
||||
else:
|
||||
f_w.write(line)
|
||||
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
dut.expect_exact('function-tracing: Hello from function_tracing example!', timeout=5)
|
||||
with (
|
||||
openocd_dut.run() as openocd,
|
||||
open(gdb_logfile, 'w') as gdb_log,
|
||||
pexpect.spawn(
|
||||
f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}',
|
||||
timeout=60,
|
||||
logfile=gdb_log,
|
||||
encoding='utf-8',
|
||||
codec_errors='ignore',
|
||||
) as p,
|
||||
):
|
||||
p.expect_exact('hit Breakpoint 1, app_main ()')
|
||||
# dut has been restarted by gdb since the last dut.expect()
|
||||
dut.expect(re.compile(rb'function-tracing: workload iteration \d+'), timeout=30)
|
||||
|
||||
# Let function-trace samples accumulate while recording.
|
||||
time.sleep(1)
|
||||
openocd.write('esp sysview_mcore stop')
|
||||
openocd.apptrace_wait_stop()
|
||||
|
||||
_validate_trace_data(trace_log, dut.target, dual_core)
|
||||
_validate_function_trace(trace_log, idf_path, dut.app.elf_file, dut.app.binary_path)
|
||||
|
||||
|
||||
@pytest.mark.jtag
|
||||
@idf_parametrize('config', ['jtag'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||
def test_function_tracing_jtag(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
_test_function_tracing_jtag(openocd_dut, idf_path, dut)
|
||||
|
||||
|
||||
@pytest.mark.usb_serial_jtag
|
||||
@idf_parametrize('config', ['jtag'], indirect=['config'])
|
||||
@idf_parametrize(
|
||||
'target',
|
||||
soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'),
|
||||
indirect=['target'],
|
||||
)
|
||||
@idf_parametrize('port', ['/dev/serial_ports/ttyUSB-esp32'], indirect=['port'])
|
||||
def test_function_tracing_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||
_test_function_tracing_jtag(openocd_dut, idf_path, dut)
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y
|
||||
CONFIG_APPTRACE_DEST_JTAG=y
|
||||
CONFIG_APPTRACE_BUF_SIZE=32768
|
||||
@@ -0,0 +1,12 @@
|
||||
# 1ms tick period
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
# Enable SystemView tracing by default
|
||||
CONFIG_ESP_TRACE_ENABLE=y
|
||||
CONFIG_ESP_TRACE_LIB_EXTERNAL=y
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_START_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_STOP_EXEC_ENABLE=y
|
||||
CONFIG_SEGGER_SYSVIEW_EVT_TASK_CREATE_ENABLE=y
|
||||
|
||||
# Compiler-instrumented function tracing
|
||||
CONFIG_ESP_TRACE_FUNCTION_TRACE=y
|
||||
Reference in New Issue
Block a user