mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
test(esp_riscv_trace): Add coredump snapshot note coverage
Check the ESP_RISCV_TRACE ELF note and per-core PT_LOAD segments after an abort, without decoding the trace stream.
This commit is contained in:
@@ -215,9 +215,13 @@ menu "RISC-V Trace Encoder Configurations"
|
||||
choice ESP_RISCV_TRACE_AHB_BURST_SELECT
|
||||
prompt "AHB burst type"
|
||||
depends on SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
default ESP_RISCV_TRACE_AHB_BURST_INCR4 if ESP_RISCV_TRACE_BUFFER_IN_EXTERNAL
|
||||
default ESP_RISCV_TRACE_AHB_BURST_SINGLE
|
||||
help
|
||||
AHB burst type used by the trace write master.
|
||||
AHB burst type used by the trace write master. Single-beat
|
||||
writes are too slow for PSRAM and lose most of the trace, so
|
||||
the default is INCR4 when the buffer is in PSRAM. Switch back
|
||||
to single if your board reports PSRAM controller errors.
|
||||
|
||||
config ESP_RISCV_TRACE_AHB_BURST_SINGLE
|
||||
bool "Single transfer"
|
||||
|
||||
@@ -249,7 +249,6 @@ err_alloc:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Common start work. Lock must be held. */
|
||||
static esp_err_t trace_start_locked(esp_riscv_trace_handle_t handle)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -266,7 +265,6 @@ static esp_err_t trace_start_locked(esp_riscv_trace_handle_t handle)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Common stop work. Lock must be held. Writes the final snapshot fields on the way out. */
|
||||
static esp_err_t trace_stop_locked(esp_riscv_trace_handle_t handle, uint32_t timeout_us)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
@@ -64,8 +64,11 @@ static bool core_buffer_stable(const esp_riscv_trace_snapshot_core_desc_t *cd)
|
||||
if (!cd->head_valid) {
|
||||
return false;
|
||||
}
|
||||
/* Bytes up to head_offset are already committed to memory. A non-empty
|
||||
* FIFO at freeze time only means the tail may be truncated. */
|
||||
if (!cd->fifo_empty) {
|
||||
return false;
|
||||
ESP_DRAM_LOGD(TAG, "core %d FIFO not empty at freeze (tail may be truncated, head=%u/%u)",
|
||||
(int)cd->core_id, (unsigned)cd->head_offset, (unsigned)cd->capacity);
|
||||
}
|
||||
return range_readable(cd->buffer_addr, cd->capacity);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ typedef struct {
|
||||
bool fifo_overflow;
|
||||
} esp_riscv_trace_snapshot_panic_core_t;
|
||||
|
||||
/* IRAM. No lock. Skips cores that are not present. */
|
||||
void esp_riscv_trace_snapshot_panic_write(const esp_riscv_trace_snapshot_panic_core_t *cores, int count);
|
||||
|
||||
/* Waits for the encoder FIFOs to empty and records the final per-core status. */
|
||||
|
||||
@@ -165,11 +165,13 @@ tools/test_apps/system/panic/coredump:
|
||||
disable:
|
||||
- if: CONFIG_NAME in ["coredump_flash_extram_stack_bss", "coredump_flash_extram_stack_heap"] and SOC_SPIRAM_SUPPORTED != 1
|
||||
- if: CONFIG_NAME == "coredump_flash_extram_stack_bss_xip" and SOC_SPIRAM_XIP_SUPPORTED != 1
|
||||
- if: CONFIG_NAME == "coredump_flash_riscv_trace" and SOC_RISCV_TRACE_SUPPORTED != 1
|
||||
- if: IDF_TARGET in ["esp32h21"]
|
||||
temporary: true
|
||||
reason: IDF-11543
|
||||
depends_components:
|
||||
- espcoredump
|
||||
- esp_riscv_trace
|
||||
- esp_system
|
||||
- spi_flash
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(COMPONENTS main esp_gdbstub espcoredump)
|
||||
set(COMPONENTS main esp_gdbstub espcoredump esp_riscv_trace)
|
||||
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../common")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
@@ -66,6 +68,7 @@ CONFIG_GDBSTUB_COREDUMP = panic_tests.configs_for_app(COREDUMP_APP, ['gdbstub_co
|
||||
# faults in idle-task context, whose small stack overflows the FreeRTOS end-of-stack watchpoint
|
||||
# if the coredump runs in place, causing a double panic. Do not switch back to coredump_flash_default.
|
||||
CONFIG_TCB_CORRUPTED = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_flash_custom_stack'])
|
||||
CONFIG_RISCV_TRACE = panic_tests.configs_for_app(COREDUMP_APP, ['coredump_flash_riscv_trace'])
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@@ -374,3 +377,94 @@ def test_tcb_corrupted(dut: PanicTestDut, target: str, config: str, test_func_na
|
||||
coredump_pattern = [re.compile(pattern.decode('utf-8')) for pattern in regex_patterns]
|
||||
|
||||
common_test(dut, config, expected_backtrace=None, expected_coredump=coredump_pattern)
|
||||
|
||||
|
||||
ESP_RISCV_TRACE_NOTE_NAME = 'ESP_RISCV_TRACE'
|
||||
ESP_RISCV_TRACE_NOTE_TYPE = 680
|
||||
ESP_RISCV_TRACE_NOTE_MAGIC = 0x53545652
|
||||
ESP_RISCV_TRACE_NOTE_HEADER_SIZE = 80
|
||||
ESP_RISCV_TRACE_NOTE_RECORD_SIZE = 44
|
||||
ESP_RISCV_TRACE_NOTE_NO_SEGMENT = 0xFFFFFFFF
|
||||
ESP_RISCV_TRACE_CAPTURE_REASON_PANIC = 2
|
||||
ESP_RISCV_TRACE_SNAPSHOT_STATE_FROZEN = 4
|
||||
|
||||
|
||||
def _riscv_trace_note_desc(elf: ELFFile) -> bytes:
|
||||
for seg in elf.iter_segments():
|
||||
if seg.header['p_type'] != 'PT_NOTE':
|
||||
continue
|
||||
for note in seg.iter_notes():
|
||||
name = note['n_name'].rstrip('\x00')
|
||||
if name == ESP_RISCV_TRACE_NOTE_NAME and note['n_type'] == ESP_RISCV_TRACE_NOTE_TYPE:
|
||||
desc = note['n_desc']
|
||||
return desc.encode('latin1') if isinstance(desc, str) else bytes(desc)
|
||||
raise AssertionError('coredump ELF has no ESP_RISCV_TRACE note')
|
||||
|
||||
|
||||
def _check_riscv_trace_coredump(core_path: str, expected_cores: int) -> None:
|
||||
with open(core_path, 'rb') as f:
|
||||
assert f.read(4) == b'\x7fELF', f'{core_path} is not an ELF file'
|
||||
f.seek(0)
|
||||
elf = ELFFile(f)
|
||||
desc = _riscv_trace_note_desc(elf)
|
||||
assert len(desc) >= ESP_RISCV_TRACE_NOTE_HEADER_SIZE
|
||||
|
||||
magic, write_seq = struct.unpack_from('<II', desc, 0)
|
||||
abi_major, abi_minor, hdr_sz, rec_sz, core_count, reason, sha_sz, params_sz = struct.unpack_from(
|
||||
'<8B', desc, 12
|
||||
)
|
||||
assert magic == ESP_RISCV_TRACE_NOTE_MAGIC
|
||||
assert (write_seq & 1) == 0
|
||||
assert abi_major == 1 and abi_minor == 0
|
||||
assert hdr_sz == ESP_RISCV_TRACE_NOTE_HEADER_SIZE
|
||||
assert rec_sz == ESP_RISCV_TRACE_NOTE_RECORD_SIZE
|
||||
assert core_count == expected_cores
|
||||
assert reason == ESP_RISCV_TRACE_CAPTURE_REASON_PANIC
|
||||
assert sha_sz == 32 and params_sz == 28
|
||||
assert len(desc) == ESP_RISCV_TRACE_NOTE_HEADER_SIZE + core_count * ESP_RISCV_TRACE_NOTE_RECORD_SIZE
|
||||
|
||||
for i in range(core_count):
|
||||
off = ESP_RISCV_TRACE_NOTE_HEADER_SIZE + i * ESP_RISCV_TRACE_NOTE_RECORD_SIZE
|
||||
core_id, state, _mem, _pkt, _addr, _resync, reserved0, head_valid, fifo_empty = struct.unpack_from(
|
||||
'<9B', desc, off
|
||||
)
|
||||
seg_index, buf_addr, capacity, seg_size, head_offset = struct.unpack_from('<5I', desc, off + 12)
|
||||
|
||||
assert core_id == i
|
||||
assert state == ESP_RISCV_TRACE_SNAPSHOT_STATE_FROZEN
|
||||
assert reserved0 == 0
|
||||
assert head_valid == 1
|
||||
assert fifo_empty == 1
|
||||
assert seg_index != ESP_RISCV_TRACE_NOTE_NO_SEGMENT
|
||||
assert buf_addr != 0
|
||||
assert capacity >= 2048 and capacity % 4 == 0
|
||||
assert seg_size == capacity
|
||||
assert 0 < head_offset <= capacity
|
||||
|
||||
# Match the trace buffer by address and size.
|
||||
matches = [
|
||||
seg
|
||||
for seg in elf.iter_segments()
|
||||
if seg.header['p_type'] == 'PT_LOAD'
|
||||
and seg.header['p_vaddr'] == buf_addr
|
||||
and seg.header['p_filesz'] == capacity
|
||||
]
|
||||
assert len(matches) == 1, f'core {core_id} expected one PT_LOAD at 0x{buf_addr:x} size {capacity}'
|
||||
assert len(matches[0].data()) == capacity
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('app_path, config', CONFIG_RISCV_TRACE, indirect=True)
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_RISCV_TRACE_SUPPORTED == 1'), indirect=['target'])
|
||||
def test_riscv_trace_coredump(dut: PanicTestDut, config: str) -> None:
|
||||
dut.run_test_func('test_abort')
|
||||
regex_pattern = rb'abort\(\) was called at PC [0-9xa-f]+ on core 0'
|
||||
dut.expect(regex_pattern)
|
||||
dut.expect_stack_dump()
|
||||
dut.expect_elf_sha256()
|
||||
dut.expect_none(['Guru Meditation', 'Re-entered core dump'])
|
||||
|
||||
coredump_pattern = re.compile(PANIC_ABORT_PREFIX + regex_pattern.decode('utf-8'))
|
||||
expect_coredump_flash_write_logs(dut, config)
|
||||
core_path = dut.process_coredump_flash([coredump_pattern])
|
||||
_check_riscv_trace_coredump(core_path, 2 if dut.is_multi_core else 1)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
|
||||
CONFIG_ESP_RISCV_TRACE_ENABLE=y
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
Reference in New Issue
Block a user