esp32: Adds functionality for application tracing over JTAG

- Implements application tracing module which allows to send arbitrary
   data to host over JTAG. This feature is useful for analyzing
   program modules behavior, dumping run-time application data etc.
 - Implements printf-like logging functions on top of apptrace module.
   This feature is a kind of semihosted printf functionality with lower
   overhead and impact on system behaviour as compared to standard printf.
This commit is contained in:
Alexey Gerenkov 2017-01-25 19:35:28 +03:00
parent ad50a70440
commit 55f1a63faf
22 changed files with 5135 additions and 61 deletions

View File

@ -56,12 +56,6 @@ config ESP32_TRAX_TWOBANKS
of memory that can't be used for general purposes anymore. Disable this if you do not know
what this is.
config ESP32_APP_TRACE
bool "Use two trace memory banks for application level trace"
default "n"
select MEMMAP_TRACEMEM
select MEMMAP_TRACEMEM_TWOBANKS
# Memory to reverse for trace, used in linker script
config TRACEMEM_RESERVE_DRAM
hex
@ -110,6 +104,45 @@ config ESP32_CORE_DUMP_LOG_LEVEL
help
Config core dump module logging level (0-5).
choice ESP32_APPTRACE_DESTINATION
prompt "AppTrace: destination"
default ESP32_APPTRACE_DEST_NONE
help
Select destination for application trace: trace memory, uart or none (to disable).
config ESP32_APPTRACE_DEST_TRAX
bool "Trace memory"
select ESP32_APPTRACE_ENABLE
config ESP32_APPTRACE_DEST_UART
bool "UART"
select ESP32_APPTRACE_ENABLE
config ESP32_APPTRACE_DEST_NONE
bool "None"
endchoice
config ESP32_APPTRACE_ENABLE
bool
depends on !ESP32_TRAX
select MEMMAP_TRACEMEM
select MEMMAP_TRACEMEM_TWOBANKS
default F
help
Enables/disable application tracing module.
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO
int "AppTrace: Timeout for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_ENABLE
default 4294967295
help
Timeout for flushing last trace data to host in case of panic. In us.
config ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH
int "AppTrace: Threshold for flushing last trace data to host on panic"
depends on ESP32_APPTRACE_DEST_TRAX
default 50
help
Threshold for flushing last trace data to host on panic. In percents of TRAX memory block length.
# Not implemented and/or needs new silicon rev to work
config MEMMAP_SPISRAM
bool "Use external SPI SRAM chip as main memory"

View File

@ -0,0 +1,994 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Hot It Works
// ************
// 1. Components Overview
// ======================
// Xtensa has useful feature: TRAX debug module. It allows recording program execution flow during run-time without disturbing CPU commands flow.
// Exectution flow data are written to configurable Trace RAM block. Besides accessing Trace RAM itself TRAX module also allows to read/write
// trace memory via its registers by means of JTAG, APB or ERI transactions.
// ESP32 has two Xtensa cores with separate TRAX modules on them and provides two special memory regions to be used as trace memory.
// ESP32 allows muxing access to trace memory blocks in such a way that while one block is accessed by CPUs another can be accessed via JTAG by host
// via reading/writing TRAX registers. Block muxing is configurable at run-time and allows switching trace memory blocks between
// accessors in round-robin fashion so they can read/write separate memory blocks without disturbing each other.
// This moduile implements application tracing feature based on above mechanisms. This feature allows to transfer arbitrary user data to
// host via JTAG with minimal impact on system performance. This module is implied to be used in the following tracing scheme.
// ------>------ ----- (host components) -----
// | | | |
// --------------- ----------------------- ----------------------- ---------------- ------ --------- -----------------
// |apptrace user|-->|target tracing module|<--->|TRAX_MEM0 | TRAX_MEM1|---->|TRAX_DATA_REGS|<-->|JTAG|<--->|OpenOCD|-->|trace data file|
// --------------- ----------------------- ----------------------- ---------------- ------ --------- -----------------
// | | | |
// | ------<------ ---------------- |
// |<------------------------------------------->|TRAX_CTRL_REGS|<---->|
// ----------------
// In general tracing happens in the following way. User aplication requests tracing module to send some data by calling esp_apptrace_buffer_get(),
// moduile allocates necessary buffer in current input trace block. Then user fills received buffer with data and calls esp_apptrace_buffer_put().
// When current input trace block is filled with app data it is exposed to host and the second block becomes input one and buffer filling restarts.
// While target application fills one memory block host reads another block via JTAG.
// To control buffer switching and for other communication purposes this implementation uses some TRAX registers. It is safe since HW TRAX tracing
// can not be used along with application tracing feature so these registers are freely readable/writeable via JTAG from host and via ERI from ESP32 cores.
// So this implementation's target CPU overhead is produced only by calls to allocate/manage buffers and data copying.
// On host special OpenOCD command must be used to read trace data.
// 2.1.1.1 TRAX Registers layout
// =============================
// This module uses two TRAX HW registers to communicate with host SW (OpenOCD).
// - Control register uses TRAX_DELAYCNT as storage. Only lower 24 bits of TRAX_DELAYCNT are writable. Control register has the following bitfields:
// | 31..XXXXXX..24 | 23 .(host_connect). 23| 22..(block_id)..15 | 14..(block_len)..0 |
// 14..0 bits - actual length of user data in trace memory block. Target updates it every time it fills memory block and exposes it to host.
// Host writes zero to this field when it finishes reading exposed block;
// 22..15 bits - trace memory block transfer ID. Block counter. It can overflow. Updated by target, host should not modify it. Actually can be 1-2 bits;
// 23 bit - 'host connected' flag. If zero then host is not connected and tracing module works in post-mortem mode, otherwise in streaming mode;
// - Status register uses TRAX_TRIGGERPC as storage. If this register is not zero then currentlly CPU is changing TRAX registers and
// this register holds address of the instruction which application will execute when it finishes with those registers modifications.
// See 'Targets Connection' setion for details.
// 3. Modes of operation
// =====================
// This module supports two modes of operation:
// - Post-mortem mode. This is the default mode. In this mode application tracing module does not check whether host has read all the data from block
// exposed to it and switches block in any case. The mode does not need host interaction for operation and so can be useful when only the latest
// trace data are necessary, e.g. for analyzing crashes. On panic the latest data from current input block are exposed to host and host can read them.
// There is menuconfig option CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH which control the threshold for flushing data on panic.
// - Streaming mode. Tracing module enters this mode when host connects to targets and sets respective bit in control register. In this mode tracing
// module waits for specified time until host read all the data from exposed block.
// On panic tracing module waits (timeout is configured via menuconfig via ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO) for the host to read all data
// from the previously exposed block.
// 4. Communication Protocol
// =========================
// 4.1 Trace Memory Blocks
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Communication is controlled via special register. Host periodically polls control register on each core to find out if there are any data avalable.
// When current input trace memory block is filled tracing module exposes block to host and updates block_len and block_id fields in control register.
// Host reads new register value and according to it starts reading data from exposed block. Meanwhile target starts filling another trace block.
// When host finishes reading the block it clears block_len field in control register indicating to target that it is ready to accept the next block.
// 4.2 User Data Chunks Level
// --------------------------
// Since trace memory block is shared between user data chunks and data copying is performed on behalf of the API user (in its normal context) in
// multithreading environment it can happen that task/ISR which copies data is preempted by another high prio task/ISR. So it is possible situation
// that task/ISR will fail to complete filling its data chunk before the whole trace block is exposed to the host. To handle such conditions tracing
// module prepends all user data chunks with 4 bytes header which contains allocated buffer size and actual data length within it. OpenOCD command
// which reads application traces will report error when it will read incompleted user data block.
// 4.3 Targets Connection/Disconnection
// ------------------------------------
// When host is going to start tracing in streaming mode it needs to put both ESP32 cores into initial state when 'host connected' bit is set
// on both cores. To accomplish this host halts both cores and sets this bit in TRAX registers. But target code can be halted in state when it has read control
// register but has not updated its value. To handle such situations target code indicates to the host that it is updating control register by writing
// non-zero value to status register. Actually it writes address of the instruction which it will execute when it finishes with
// the registers update. When target is halted during control register update host sets breakpoint at the address from status register and resumes CPU.
// After target code finishes with register update it is halted on breakpoint, host detects it and safely sets 'host connected' bit. When both cores
// are set up they are resumed. Tracing starts without further intrusion into CPUs work.
// When host is going to stop tracing in streaming mode it needs to disconnect targets. Disconnection process is done using the same algorithm
// as for connecting, but 'host connected' bits are cleared on ESP32 cores.
// 5. Module Access Synchronization
// ================================
// Access to internal module's data is synchronized with custom mutex. Mutex is a wrapper for portMUX_TYPE and uses almost the same sync mechanism as in
// vPortCPUAcquireMutex/vPortCPUReleaseMutex. The mechanism uses S32C1I Xtensa instruction to implement exclusive access to module's data from tasks and
// ISRs running on both cores. Also custom mutex allows specifying timeout for locking operation. Locking routine checks underlaying mutex in cycle until
// it gets its ownership or timeout expires. The differences of application tracing module's mutex implementation from vPortCPUAcquireMutex/vPortCPUReleaseMutex are:
// - Support for timeouts.
// - Local IRQs for CPU which owns the mutex are disabled till the call to unlocking routine. This is made to avoid possible task's prio inversion.
// When low prio task takes mutex and enables local IRQs gets preempted by high prio task which in its turn can try to acquire mutex using infinite timeout.
// So no local task switch occurs when mutex is locked. But this does not apply to tasks on another CPU.
// WARNING: Priority inversion can happen when low prio task works on one CPU and medium and high prio tasks work on another.
// There are some differences how mutex behaves when it is used from task and ISR context when timeout is non-zero:
// - In task context when mutex can not be locked portYIELD() is called before check for timeout condition to alow othet tasks work on the same CPU.
// - In ISR context when mutex can not be locked nothing is done before expired time check.
// WARNING: Care must be taken when selecting timeout values for trace calls from ISRs. Tracing module does not care about watchdogs when waiting on internal locks
// and when waiting for host to complete previous block reading, so if wating timeout value exceedes watchdog's one it can lead to system reboot.
// 6. Timeouts
// ------------
// Timeout mechanism is based on xthal_get_ccount() routine and supports timeout values in micorseconds.
// There are two situations when task/ISR can be delayed by tracing API call. Timeout mechanism takes into account both conditions:
// - Trace data are locked by another task/ISR. When wating on trace data lock.
// - Current TRAX memory input block is full when working in streaming mode (host is connected). When waiting for host to complete previous block reading.
// When wating for any of above conditions xthal_get_ccount() is called periodically to calculate time elapsed from trace API routine entry. When elapsed
// time exceeds specified timeout value operation is canceled and ESP_ERR_TIMEOUT code is returned.
// ALSO SEE example usage of application tracing module in 'components/log/README.rst'
#include <string.h>
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "eri.h"
#include "trax.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "esp_app_trace.h"
#if CONFIG_ESP32_APPTRACE_ENABLE
#define ESP_APPTRACE_DEBUG_STATS_ENABLE 0
#define ESP_APPTRACE_BUF_HISTORY_DEPTH (16*100)
#define ESP_APPTRACE_MAX_VPRINTF_ARGS 256
#define ESP_APPTRACE_PRINT_LOCK_NONE 0
#define ESP_APPTRACE_PRINT_LOCK_SEM 1
#define ESP_APPTRACE_PRINT_LOCK_MUX 2
#define ESP_APPTRACE_PRINT_LOCK ESP_APPTRACE_PRINT_LOCK_NONE//ESP_APPTRACE_PRINT_LOCK_SEM
#define ESP_APPTRACE_USE_LOCK_SEM 0 // 1 - semaphore (now may be broken), 0 - portMUX_TYPE
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"
const static char *TAG = "esp_apptrace";
#if ESP_APPTRACE_PRINT_LOCK != ESP_APPTRACE_PRINT_LOCK_NONE
#define ESP_APPTRACE_LOG( format, ... ) \
do { \
esp_apptrace_log_lock(); \
ets_printf(format, ##__VA_ARGS__); \
esp_apptrace_log_unlock(); \
} while(0)
#else
#define ESP_APPTRACE_LOG( format, ... ) \
do { \
ets_printf(format, ##__VA_ARGS__); \
} while(0)
#endif
#define ESP_APPTRACE_LOG_LEV( _L_, level, format, ... ) \
do { \
if (LOG_LOCAL_LEVEL >= level) { \
ESP_APPTRACE_LOG(LOG_FORMAT(_L_, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); \
} \
} while(0)
#define ESP_APPTRACE_LOGE( format, ... ) ESP_APPTRACE_LOG_LEV(E, ESP_LOG_ERROR, format, ##__VA_ARGS__)
#define ESP_APPTRACE_LOGW( format, ... ) ESP_APPTRACE_LOG_LEV(W, ESP_LOG_WARN, format, ##__VA_ARGS__)
#define ESP_APPTRACE_LOGI( format, ... ) ESP_APPTRACE_LOG_LEV(I, ESP_LOG_INFO, format, ##__VA_ARGS__)
#define ESP_APPTRACE_LOGD( format, ... ) ESP_APPTRACE_LOG_LEV(D, ESP_LOG_DEBUG, format, ##__VA_ARGS__)
#define ESP_APPTRACE_LOGV( format, ... ) ESP_APPTRACE_LOG_LEV(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__)
#define ESP_APPTRACE_LOGO( format, ... ) ESP_APPTRACE_LOG_LEV(E, ESP_LOG_NONE, format, ##__VA_ARGS__)
#define ESP_APPTRACE_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000))
// TODO: move these (and same definitions in trax.c to dport_reg.h)
#define TRACEMEM_MUX_PROBLK0_APPBLK1 0
#define TRACEMEM_MUX_BLK0_ONLY 1
#define TRACEMEM_MUX_BLK1_ONLY 2
#define TRACEMEM_MUX_PROBLK1_APPBLK0 3
// TRAX is disabled, so we use its registers for our own purposes
// | 31..XXXXXX..24 | 23 .(host_connect). 23| 22..(block_id)..15 | 14..(block_len)..0 |
#define ESP_APPTRACE_TRAX_CTRL_REG ERI_TRAX_DELAYCNT
#define ESP_APPTRACE_TRAX_STAT_REG ERI_TRAX_TRIGGERPC
#define ESP_APPTRACE_TRAX_BLOCK_LEN_MSK 0x7FFFUL
#define ESP_APPTRACE_TRAX_BLOCK_LEN(_l_) ((_l_) & ESP_APPTRACE_TRAX_BLOCK_LEN_MSK)
#define ESP_APPTRACE_TRAX_BLOCK_LEN_GET(_v_) ((_v_) & ESP_APPTRACE_TRAX_BLOCK_LEN_MSK)
#define ESP_APPTRACE_TRAX_BLOCK_ID_MSK 0xFFUL
#define ESP_APPTRACE_TRAX_BLOCK_ID(_id_) (((_id_) & ESP_APPTRACE_TRAX_BLOCK_ID_MSK) << 15)
#define ESP_APPTRACE_TRAX_BLOCK_ID_GET(_v_) (((_v_) >> 15) & ESP_APPTRACE_TRAX_BLOCK_ID_MSK)
#define ESP_APPTRACE_TRAX_HOST_CONNECT (1 << 23)
static volatile uint8_t *s_trax_blocks[] = {
(volatile uint8_t *) 0x3FFFC000,
(volatile uint8_t *) 0x3FFF8000
};
#define ESP_APPTRACE_TRAX_BLOCKS_NUM (sizeof(s_trax_blocks)/sizeof(s_trax_blocks[0]))
//#define ESP_APPTRACE_TRAX_BUFFER_SIZE (ESP_APPTRACE_TRAX_BLOCK_SIZE/4)
#define ESP_APPTRACE_TRAX_INBLOCK_START 0//(ESP_APPTRACE_TRAX_BLOCK_ID_MSK - 4)
#define ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET() (&s_trace_buf.trax.state.markers[s_trace_buf.trax.state.in_block % 2])
#define ESP_APPTRACE_TRAX_INBLOCK_GET() (&s_trace_buf.trax.blocks[s_trace_buf.trax.state.in_block % 2])
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
/** keeps info about apptrace API (write/get buffer) caller and internal module's data related to that call
* NOTE: used for module debug purposes, currently this functionality is partially broken,
* but can be useful in future
*/
typedef struct {
uint32_t hnd; // task/ISR handle
uint32_t ts; // timestamp
uint32_t stamp; // test (user) trace buffer stamp
uint32_t in_block; // TRAX input block ID
uint32_t eri_len[2]; // contents of ERI control register upon entry to / exit from API routine
uint32_t wr_err; // number of trace write errors
} esp_trace_buffer_wr_hitem_t;
/** apptrace API calls history. History is organized as ring buffer*/
typedef struct {
uint32_t hist_rd; // the first history entry index
uint32_t hist_wr; // the last history entry index
esp_trace_buffer_wr_hitem_t hist[ESP_APPTRACE_BUF_HISTORY_DEPTH]; // history data
} esp_trace_buffer_wr_stats_t;
/** trace module stats */
typedef struct {
esp_trace_buffer_wr_stats_t wr;
} esp_trace_buffer_stats_t;
#endif
/** Trace data header. Every user data chunk is prepended with this header.
* User allocates block with esp_apptrace_buffer_get and then fills it with data,
* in multithreading environment it can happen that tasks gets buffer and then gets interrupted,
* so it is possible that user data are incomplete when TRAX memory block is exposed to the host.
* In this case host SW will see that wr_sz < block_sz and will report error.
*/
typedef struct {
uint16_t block_sz; // size of allocated block for user data
uint16_t wr_sz; // size of actually written data
} esp_tracedata_hdr_t;
/** TRAX HW transport state */
typedef struct {
uint32_t in_block; // input block ID
uint32_t markers[ESP_APPTRACE_TRAX_BLOCKS_NUM]; // block filling level markers
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
esp_trace_buffer_stats_t stats; // stats
#endif
} esp_apptrace_trax_state_t;
/** memory block parameters */
typedef struct {
uint8_t *start; // start address
uint32_t sz; // size
} esp_apptrace_mem_block_t;
/** TRAX HW transport data */
typedef struct {
volatile esp_apptrace_trax_state_t state; // state
esp_apptrace_mem_block_t blocks[ESP_APPTRACE_TRAX_BLOCKS_NUM]; // memory blocks
} esp_apptrace_trax_data_t;
/** tracing module synchronization lock */
typedef struct {
volatile unsigned int irq_stat; // local (on 1 CPU) IRQ state
portMUX_TYPE portmux; // mux for synchronization
} esp_apptrace_lock_t;
#define ESP_APPTRACE_MUX_GET(_m_) (&(_m_)->portmux)
/** tracing module internal data */
typedef struct {
#if ESP_APPTRACE_USE_LOCK_SEM == 1
SemaphoreHandle_t lock;
#else
esp_apptrace_lock_t lock; // sync lock
#endif
uint8_t inited; // module initialization state flag
esp_apptrace_trax_data_t trax; // TRAX HW transport data
} esp_apptrace_buffer_t;
/** waiting timeout data */
typedef struct {
uint32_t start; // waiting start (in ticks)
uint32_t tmo; // timeout (in us)
} esp_apptrace_tmo_t;
static esp_apptrace_buffer_t s_trace_buf;
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
static SemaphoreHandle_t s_log_lock;
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
static esp_apptrace_lock_t s_log_lock;
#endif
static inline void esp_apptrace_tmo_init(esp_apptrace_tmo_t *tmo, uint32_t user_tmo)
{
tmo->start = xthal_get_ccount();
tmo->tmo = user_tmo;
}
static esp_err_t esp_apptrace_tmo_check(esp_apptrace_tmo_t *tmo)
{
unsigned cur, elapsed;
if (tmo->tmo != ESP_APPTRACE_TMO_INFINITE) {
cur = xthal_get_ccount();
if (tmo->start <= cur) {
elapsed = cur - tmo->start;
} else {
elapsed = 0xFFFFFFFF - tmo->start + cur;
}
if (ESP_APPTRACE_CPUTICKS2US(elapsed) >= tmo->tmo) {
return ESP_ERR_TIMEOUT;
}
}
return ESP_OK;
}
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX || ESP_APPTRACE_USE_LOCK_SEM == 0
static inline void esp_apptrace_mux_init(esp_apptrace_lock_t *mux)
{
ESP_APPTRACE_MUX_GET(mux)->mux = portMUX_FREE_VAL;
mux->irq_stat = 0;
}
static esp_err_t esp_apptrace_lock_take(esp_apptrace_lock_t *mux, uint32_t tmo)
{
uint32_t res = ~portMUX_FREE_VAL;
esp_apptrace_tmo_t sleeping_tmo;
esp_apptrace_tmo_init(&sleeping_tmo, tmo);
while (1) {
res = (xPortGetCoreID() << portMUX_VAL_SHIFT) | portMUX_MAGIC_VAL;
// first disable IRQs on this CPU, this will prevent current task from been
// preempted by higher prio tasks, otherwise deadlock can happen:
// when lower prio task took mux and then preempted by higher prio one which also tries to
// get mux with INFINITE timeout
unsigned int irq_stat = portENTER_CRITICAL_NESTED();
// Now try to lock mux
uxPortCompareSet(&ESP_APPTRACE_MUX_GET(mux)->mux, portMUX_FREE_VAL, &res);
if (res == portMUX_FREE_VAL) {
// do not enable IRQs, we will held them disabled until mux is unlocked
// we do not need to flush cache region for mux->irq_stat because it is used
// to hold and restore IRQ state only for CPU which took mux, other CPUs will not use this value
mux->irq_stat = irq_stat;
break;
}
// if mux is locked by other task/ISR enable IRQs and let other guys work
portEXIT_CRITICAL_NESTED(irq_stat);
if (!xPortInIsrContext()) {
portYIELD();
}
int err = esp_apptrace_tmo_check(&sleeping_tmo);
if (err != ESP_OK) {
return err;
}
}
return ESP_OK;
}
esp_err_t esp_apptrace_mux_give(esp_apptrace_lock_t *mux)
{
esp_err_t ret = ESP_OK;
uint32_t res = 0;
unsigned int irq_stat;
res = portMUX_FREE_VAL;
// first of all save a copy of IRQ status for this locker because uxPortCompareSet will unlock mux and tasks/ISRs
// from other core can overwrite mux->irq_stat
irq_stat = mux->irq_stat;
uxPortCompareSet(&ESP_APPTRACE_MUX_GET(mux)->mux, (xPortGetCoreID() << portMUX_VAL_SHIFT) | portMUX_MAGIC_VAL, &res);
// enable local interrupts
portEXIT_CRITICAL_NESTED(irq_stat);
if ( ((res & portMUX_VAL_MASK) >> portMUX_VAL_SHIFT) == xPortGetCoreID() ) {
// nothing to do
} else if ( res == portMUX_FREE_VAL ) {
ret = ESP_FAIL; // should never get here
} else {
ret = ESP_FAIL; // should never get here
}
return ret;
}
#endif
static inline esp_err_t esp_apptrace_log_init()
{
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
s_log_lock = xSemaphoreCreateBinary();
if (!s_log_lock) {
ets_printf("%s: Failed to create print lock sem!", TAG);
return ESP_FAIL;
}
xSemaphoreGive(s_log_lock);
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
esp_apptrace_mux_init(&s_log_lock);
#endif
return ESP_OK;
}
static inline void esp_apptrace_log_cleanup()
{
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
vSemaphoreDelete(s_log_lock);
#endif
}
static inline int esp_apptrace_log_lock()
{
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
BaseType_t ret;
if (xPortInIsrContext()) {
ret = xSemaphoreTakeFromISR(s_print_lock, NULL);
} else {
ret = xSemaphoreTake(s_print_lock, portMAX_DELAY);
}
return ret;
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
int ret = esp_apptrace_lock_take(&s_log_lock, ESP_APPTRACE_TMO_INFINITE);
return ret;
#endif
return 0;
}
static inline void esp_apptrace_log_unlock()
{
#if ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_SEM
if (xPortInIsrContext()) {
xSemaphoreGiveFromISR(s_log_lock, NULL);
} else {
xSemaphoreGive(s_log_lock);
}
#elif ESP_APPTRACE_PRINT_LOCK == ESP_APPTRACE_PRINT_LOCK_MUX
esp_apptrace_mux_give(&s_log_lock);
#endif
}
esp_err_t esp_apptrace_lock_init()
{
#if ESP_APPTRACE_USE_LOCK_SEM == 1
s_trace_buf.lock = xSemaphoreCreateBinary();
if (!s_trace_buf.lock) {
ESP_APPTRACE_LOGE("Failed to create lock!");
return ESP_FAIL;
}
xSemaphoreGive(s_trace_buf.lock);
#else
esp_apptrace_mux_init(&s_trace_buf.lock);
#endif
return ESP_OK;
}
esp_err_t esp_apptrace_lock_cleanup()
{
#if ESP_APPTRACE_USE_LOCK_SEM == 1
vSemaphoreDelete(s_trace_buf.lock);
#endif
return ESP_OK;
}
esp_err_t esp_apptrace_lock(uint32_t *tmo)
{
unsigned cur, elapsed, start = xthal_get_ccount();
#if ESP_APPTRACE_USE_LOCK_SEM == 1
BaseType_t ret;
if (xPortInIsrContext()) {
ret = xSemaphoreTakeFromISR(s_trace_buf.lock, NULL);
} else {
ret = xSemaphoreTake(s_trace_buf.lock, portTICK_PERIOD_MS * (*tmo) / 1000);
}
if (ret != pdTRUE) {
return ESP_FAIL;
}
#else
esp_err_t ret = esp_apptrace_lock_take(&s_trace_buf.lock, *tmo);
if (ret != ESP_OK) {
return ESP_FAIL;
}
#endif
// decrease tmo by actual waiting time
cur = xthal_get_ccount();
if (start <= cur) {
elapsed = cur - start;
} else {
elapsed = ULONG_MAX - start + cur;
}
if (ESP_APPTRACE_CPUTICKS2US(elapsed) > *tmo) {
*tmo = 0;
} else {
*tmo -= ESP_APPTRACE_CPUTICKS2US(elapsed);
}
return ESP_OK;
}
esp_err_t esp_apptrace_unlock()
{
esp_err_t ret = ESP_OK;
#if ESP_APPTRACE_USE_LOCK_SEM == 1
if (xPortInIsrContext()) {
xSemaphoreGiveFromISR(s_trace_buf.lock, NULL);
} else {
xSemaphoreGive(s_trace_buf.lock);
}
#else
ret = esp_apptrace_mux_give(&s_trace_buf.lock);
#endif
return ret;
}
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
static void esp_apptrace_trax_init()
{
// Stop trace, if any (on the current CPU)
eri_write(ERI_TRAX_TRAXCTRL, TRAXCTRL_TRSTP);
eri_write(ERI_TRAX_TRAXCTRL, TRAXCTRL_TMEN);
eri_write(ESP_APPTRACE_TRAX_CTRL_REG, ESP_APPTRACE_TRAX_BLOCK_ID(ESP_APPTRACE_TRAX_INBLOCK_START));
eri_write(ESP_APPTRACE_TRAX_STAT_REG, 0);
ESP_APPTRACE_LOGI("Initialized TRAX on CPU%d", xPortGetCoreID());
}
// assumed to be protected by caller from multi-core/thread access
static esp_err_t esp_apptrace_trax_block_switch()
{
int prev_block_num = s_trace_buf.trax.state.in_block % 2;
int new_block_num = prev_block_num ? (0) : (1);
int res = ESP_OK;
extern uint32_t __esp_apptrace_trax_eri_updated;
// indicate to host that we are about to update.
// this is used only to place CPU into streaming mode at tracing startup
// before starting streaming host can halt us after we read ESP_APPTRACE_TRAX_CTRL_REG and before we updated it
// HACK: in this case host will set breakpoint just after ESP_APPTRACE_TRAX_CTRL_REG update,
// here we set address to set bp at
// enter ERI update critical section
eri_write(ESP_APPTRACE_TRAX_STAT_REG, (uint32_t)&__esp_apptrace_trax_eri_updated);
uint32_t ctrl_reg = eri_read(ESP_APPTRACE_TRAX_CTRL_REG);
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
if (s_trace_buf.state.stats.wr.hist_wr < ESP_APPTRACE_BUF_HISTORY_DEPTH) {
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[s_trace_buf.state.stats.wr.hist_wr - 1];
hi->eri_len[1] = ctrl_reg;
}
#endif
uint32_t host_connected = ESP_APPTRACE_TRAX_HOST_CONNECT & ctrl_reg;
if (host_connected) {
uint32_t acked_block = ESP_APPTRACE_TRAX_BLOCK_ID_GET(ctrl_reg);
uint32_t host_to_read = ESP_APPTRACE_TRAX_BLOCK_LEN_GET(ctrl_reg);
if (host_to_read != 0 || acked_block != (s_trace_buf.trax.state.in_block & ESP_APPTRACE_TRAX_BLOCK_ID_MSK)) {
// ESP_APPTRACE_LOGE("HC[%d]: Can not switch %x %d %x %x/%lx", xPortGetCoreID(), ctrl_reg, host_to_read, acked_block,
// s_trace_buf.trax.state.in_block & ESP_APPTRACE_TRAX_BLOCK_ID_MSK, s_trace_buf.trax.state.in_block);
res = ESP_ERR_NO_MEM;
goto _on_func_exit;
}
}
s_trace_buf.trax.state.markers[new_block_num] = 0;
// switch to new block
s_trace_buf.trax.state.in_block++;
WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, new_block_num ? TRACEMEM_MUX_BLK0_ONLY : TRACEMEM_MUX_BLK1_ONLY);
eri_write(ESP_APPTRACE_TRAX_CTRL_REG, ESP_APPTRACE_TRAX_BLOCK_ID(s_trace_buf.trax.state.in_block) |
host_connected | ESP_APPTRACE_TRAX_BLOCK_LEN(s_trace_buf.trax.state.markers[prev_block_num]));
_on_func_exit:
// exit ERI update critical section
eri_write(ESP_APPTRACE_TRAX_STAT_REG, 0x0);
asm volatile (
" .global __esp_apptrace_trax_eri_updated\n"
"__esp_apptrace_trax_eri_updated:\n"); // host will set bp here to resolve collision at streaming start
return res;
}
static esp_err_t esp_apptrace_trax_block_switch_waitus(uint32_t tmo)
{
int res;
esp_apptrace_tmo_t sleeping_tmo;
esp_apptrace_tmo_init(&sleeping_tmo, tmo);
while ((res = esp_apptrace_trax_block_switch()) != ESP_OK) {
res = esp_apptrace_tmo_check(&sleeping_tmo);
if (res != ESP_OK) {
break;
}
}
return res;
}
static uint8_t *esp_apptrace_trax_get_buffer(size_t size, uint32_t *tmo)
{
uint8_t *buf_ptr = NULL;
volatile uint32_t *cur_block_marker;
esp_apptrace_mem_block_t *cur_block;
int res = esp_apptrace_lock(tmo);
if (res != ESP_OK) {
return NULL;
}
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
esp_trace_buffer_wr_hitem_t *hi = NULL;
if (s_trace_buf.state.stats.wr.hist_wr < ESP_APPTRACE_BUF_HISTORY_DEPTH) {
hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[s_trace_buf.state.stats.wr.hist_wr++];
hi->hnd = *(uint32_t *)(buf + 0);
hi->ts = *(uint32_t *)(buf + sizeof(uint32_t));
hi->stamp = *(buf + 2 * sizeof(uint32_t));
hi->in_block = s_trace_buf.state.in_block;
hi->wr_err = 0;
hi->eri_len[0] = eri_read(ESP_APPTRACE_TRAX_CTRL_REG);
if (s_trace_buf.state.stats.wr.hist_wr == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
s_trace_buf.state.stats.wr.hist_wr = 0;
}
if (s_trace_buf.state.stats.wr.hist_wr == s_trace_buf.state.stats.wr.hist_rd) {
s_trace_buf.state.stats.wr.hist_rd++;
if (s_trace_buf.state.stats.wr.hist_rd == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
s_trace_buf.state.stats.wr.hist_rd = 0;
}
}
}
#endif
cur_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
cur_block = ESP_APPTRACE_TRAX_INBLOCK_GET();
if (*cur_block_marker + size + sizeof(esp_tracedata_hdr_t) >= cur_block->sz) {
// flush data, we can not unlock apptrace until we have buffer for all user data
// otherwise other tasks/ISRs can get control and write their data between chunks of this data
res = esp_apptrace_trax_block_switch_waitus(/*size + sizeof(esp_tracedata_hdr_t),*/*tmo);
if (res != ESP_OK) {
if (esp_apptrace_unlock() != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to unlock apptrace data!");
// there is a bug, should never get here
}
return NULL;
}
// we switched to new block, update TRAX block pointers
cur_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
cur_block = ESP_APPTRACE_TRAX_INBLOCK_GET();
}
buf_ptr = cur_block->start + *cur_block_marker;
((esp_tracedata_hdr_t *)buf_ptr)->block_sz = size;
((esp_tracedata_hdr_t *)buf_ptr)->wr_sz = 0;
*cur_block_marker += size + sizeof(esp_tracedata_hdr_t);
// now we can safely unlock apptrace to allow other tasks/ISRs to get other buffers and write their data
if (esp_apptrace_unlock() != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to unlock apptrace data!");
// there is a bug, should never get here
}
return buf_ptr + sizeof(esp_tracedata_hdr_t);
}
static esp_err_t esp_apptrace_trax_put_buffer(uint8_t *ptr, uint32_t *tmo)
{
int res = ESP_OK;
esp_tracedata_hdr_t *hdr = (esp_tracedata_hdr_t *)(ptr - sizeof(esp_tracedata_hdr_t));
// update written size
hdr->wr_sz = hdr->block_sz;
// TODO: mark block as busy in order not to re-use it for other tracing calls until it is completely written
// TODO: avoid potential situation when all memory is consumed by low prio tasks which can not complete writing due to
// higher prio tasks and the latter can not allocate buffers at all
// this is abnormal situation can be detected on host which will receive only uncompleted buffers
// workaround: use own memcpy which will kick-off dead tracing calls
return res;
}
static esp_err_t esp_apptrace_trax_flush(uint32_t min_sz, uint32_t tmo)
{
volatile uint32_t *in_block_marker;
int res = ESP_OK;
in_block_marker = ESP_APPTRACE_TRAX_INBLOCK_MARKER_PTR_GET();
if (*in_block_marker > min_sz) {
ESP_APPTRACE_LOGD("Wait until block switch for %u us", tmo);
res = esp_apptrace_trax_block_switch_waitus(/*0 query any size,*/tmo);
if (res != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to switch to another block");
return res;
}
ESP_APPTRACE_LOGD("Flushed last block %u bytes", *in_block_marker);
*in_block_marker = 0;
}
return res;
}
static esp_err_t esp_apptrace_trax_dest_init()
{
for (int i = 0; i < ESP_APPTRACE_TRAX_BLOCKS_NUM; i++) {
s_trace_buf.trax.blocks[i].start = (uint8_t *)s_trax_blocks[i];
s_trace_buf.trax.blocks[i].sz = ESP_APPTRACE_TRAX_BLOCK_SIZE;
s_trace_buf.trax.state.markers[i] = 0;
}
s_trace_buf.trax.state.in_block = ESP_APPTRACE_TRAX_INBLOCK_START;
WRITE_PERI_REG(DPORT_PRO_TRACEMEM_ENA_REG, DPORT_PRO_TRACEMEM_ENA_M);
#if CONFIG_FREERTOS_UNICORE == 0
WRITE_PERI_REG(DPORT_APP_TRACEMEM_ENA_REG, DPORT_APP_TRACEMEM_ENA_M);
#endif
// Expose block 1 to host, block 0 is current trace input buffer
WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, TRACEMEM_MUX_BLK1_ONLY);
return ESP_OK;
}
#endif
esp_err_t esp_apptrace_init()
{
int res;
if (!s_trace_buf.inited) {
res = esp_apptrace_log_init();
if (res != ESP_OK) {
ets_printf("%s: Failed to init log lock (%d)!", TAG, res);
return res;
}
//memset(&s_trace_buf, 0, sizeof(s_trace_buf));
res = esp_apptrace_lock_init(&s_trace_buf.lock);
if (res != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to init log lock (%d)!", res);
esp_apptrace_log_cleanup();
return res;
}
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
res = esp_apptrace_trax_dest_init();
if (res != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to init TRAX dest data (%d)!", res);
esp_apptrace_lock_cleanup();
esp_apptrace_log_cleanup();
return res;
}
#endif
}
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
// init TRAX on this CPU
esp_apptrace_trax_init();
#endif
s_trace_buf.inited |= 1 << xPortGetCoreID(); // global and this CPU-specific data are inited
return ESP_OK;
}
esp_err_t esp_apptrace_write(esp_apptrace_dest_t dest, void *data, size_t size, uint32_t user_tmo)
{
uint8_t *ptr = NULL;
uint32_t tmo = user_tmo;
//TODO: use ptr to HW transport iface struct
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return ESP_ERR_NOT_SUPPORTED;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return ESP_ERR_NOT_SUPPORTED;
}
ptr = apptrace_get_buffer(size, &tmo);
if (ptr == NULL) {
//ESP_APPTRACE_LOGE("Failed to get buffer!");
return ESP_ERR_NO_MEM;
}
// actually can be suspended here by higher prio tasks/ISRs
//TODO: use own memcpy with dead trace calls kick-off algo, and tmo expiration check
memcpy(ptr, data, size);
// now indicate that this buffer is ready to be sent off to host
return apptrace_put_buffer(ptr, &tmo);
}
int esp_apptrace_vprintf_to(esp_apptrace_dest_t dest, uint32_t user_tmo, const char *fmt, va_list ap)
{
uint16_t nargs = 0;
uint8_t *pout, *p = (uint8_t *)fmt;
uint32_t tmo = user_tmo;
//TODO: use ptr to HW transport iface struct
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return ESP_ERR_NOT_SUPPORTED;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return ESP_ERR_NOT_SUPPORTED;
}
// ESP_APPTRACE_LOGI("fmt %x", fmt);
while ((p = (uint8_t *)strchr((char *)p, '%')) && nargs < ESP_APPTRACE_MAX_VPRINTF_ARGS) {
p++;
if (*p != '%' && *p != 0) {
nargs++;
}
}
// ESP_APPTRACE_LOGI("nargs = %d", nargs);
if (p) {
ESP_APPTRACE_LOGE("Failed to store all printf args!");
}
pout = apptrace_get_buffer(1 + sizeof(char *) + nargs * sizeof(uint32_t), &tmo);
if (pout == NULL) {
ESP_APPTRACE_LOGE("Failed to get buffer!");
return -1;
}
p = pout;
*pout = nargs;
pout++;
*(const char **)pout = fmt;
pout += sizeof(char *);
while (nargs-- > 0) {
uint32_t arg = va_arg(ap, uint32_t);
*(uint32_t *)pout = arg;
pout += sizeof(uint32_t);
// ESP_APPTRACE_LOGI("arg %x", arg);
}
int ret = apptrace_put_buffer(p, &tmo);
if (ret != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to put printf buf (%d)!", ret);
return -1;
}
return (pout - p);
}
int esp_apptrace_vprintf(const char *fmt, va_list ap)
{
return esp_apptrace_vprintf_to(ESP_APPTRACE_DEST_TRAX, /*ESP_APPTRACE_TMO_INFINITE*/0, fmt, ap);
}
uint8_t *esp_apptrace_buffer_get(esp_apptrace_dest_t dest, size_t size, uint32_t user_tmo)
{
uint32_t tmo = user_tmo;
//TODO: use ptr to HW transport iface struct
uint8_t *(*apptrace_get_buffer)(size_t, uint32_t *);
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
apptrace_get_buffer = esp_apptrace_trax_get_buffer;
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return NULL;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return NULL;
}
return apptrace_get_buffer(size, &tmo);
}
esp_err_t esp_apptrace_buffer_put(esp_apptrace_dest_t dest, uint8_t *ptr, uint32_t user_tmo)
{
uint32_t tmo = user_tmo;
//TODO: use ptr to HW transport iface struct
esp_err_t (*apptrace_put_buffer)(uint8_t *, uint32_t *);
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
apptrace_put_buffer = esp_apptrace_trax_put_buffer;
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return ESP_ERR_NOT_SUPPORTED;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return ESP_ERR_NOT_SUPPORTED;
}
return apptrace_put_buffer(ptr, &tmo);
}
esp_err_t esp_apptrace_flush_nolock(esp_apptrace_dest_t dest, uint32_t min_sz, uint32_t tmo)
{
//TODO: use ptr to HW transport iface struct
esp_err_t (*apptrace_flush)(uint32_t, uint32_t);
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
apptrace_flush = esp_apptrace_trax_flush;
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return ESP_ERR_NOT_SUPPORTED;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return ESP_ERR_NOT_SUPPORTED;
}
return apptrace_flush(min_sz, tmo);
}
esp_err_t esp_apptrace_flush(esp_apptrace_dest_t dest, uint32_t tmo)
{
int res;
res = esp_apptrace_lock(&tmo);
if (res != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to lock apptrace data (%d)!", res);
return res;
}
res = esp_apptrace_flush_nolock(dest, 0, tmo);
if (res != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to fluch apptrace data (%d)!", res);
}
if (esp_apptrace_unlock() != ESP_OK) {
ESP_APPTRACE_LOGE("Failed to unlock apptrace data (%d)!", res);
}
return res;
}
#if ESP_APPTRACE_DEBUG_STATS_ENABLE == 1
void esp_apptrace_print_stats()
{
uint32_t i;
uint32_t tmo = ESP_APPTRACE_TMO_INFINITE;
esp_apptrace_lock(&tmo);
for (i = s_trace_buf.state.stats.wr.hist_rd; (i < s_trace_buf.state.stats.wr.hist_wr) && (i < ESP_APPTRACE_BUF_HISTORY_DEPTH); i++) {
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[i];
ESP_APPTRACE_LOGO("hist[%u] = {%x, %x}", i, hi->hnd, hi->ts);
}
if (i == ESP_APPTRACE_BUF_HISTORY_DEPTH) {
for (i = 0; i < s_trace_buf.state.stats.wr.hist_wr; i++) {
esp_trace_buffer_wr_hitem_t *hi = (esp_trace_buffer_wr_hitem_t *)&s_trace_buf.state.stats.wr.hist[i];
ESP_APPTRACE_LOGO("hist[%u] = {%x, %x}", i, hi->hnd, hi->ts);
}
}
esp_apptrace_unlock();
}
#endif
#endif

View File

@ -59,6 +59,7 @@
#include "esp_coexist.h"
#include "esp_panic.h"
#include "esp_core_dump.h"
#include "esp_app_trace.h"
#include "trax.h"
#define STRINGIFY(s) STRINGIFY2(s)
@ -221,6 +222,12 @@ void start_cpu0_default(void)
_GLOBAL_REENT->_stdin = (FILE*) &__sf_fake_stdin;
_GLOBAL_REENT->_stdout = (FILE*) &__sf_fake_stdout;
_GLOBAL_REENT->_stderr = (FILE*) &__sf_fake_stderr;
#endif
#if CONFIG_ESP32_APPTRACE_ENABLE
esp_err_t err = esp_apptrace_init();
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "Failed to init apptrace module on CPU0 (%d)!", err);
}
#endif
do_global_ctors();
#if CONFIG_INT_WDT
@ -252,6 +259,12 @@ void start_cpu1_default(void)
{
#if CONFIG_ESP32_TRAX_TWOBANKS
trax_start_trace(TRAX_DOWNCOUNT_WORDS);
#endif
#if CONFIG_ESP32_APPTRACE_ENABLE
esp_err_t err = esp_apptrace_init();
if (err != ESP_OK) {
ESP_EARLY_LOGE(TAG, "Failed to init apptrace module on CPU1 (%d)!", err);
}
#endif
// Wait for FreeRTOS initialization to finish on PRO CPU
while (port_xSchedulerRunning[0] == 0) {

View File

@ -0,0 +1,123 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ESP_APP_TRACE_H_
#define ESP_APP_TRACE_H_
#include <stdarg.h>
#include "esp_err.h"
// infinite waiting timeout
#define ESP_APPTRACE_TMO_INFINITE ((uint32_t)-1)
// Trace memory block size
#define ESP_APPTRACE_TRAX_BLOCK_SIZE 0x4000UL
/**
* Application trace data destinations bits.
*/
typedef enum {
ESP_APPTRACE_DEST_TRAX = 0x1,
ESP_APPTRACE_DEST_UART0 = 0x2,
//ESP_APPTRACE_DEST_UART1 = 0x4,
} esp_apptrace_dest_t;
/**
* @brief Initializes application tracing module.
*
* @note Should be called before any esp_apptrace_xxx call.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
esp_err_t esp_apptrace_init();
/**
* @brief Allocates buffer for trace data.
* After data in buffer are ready to be sent off esp_apptrace_buffer_put must be called to indicate it.
*
* @param dest Indicates HW interface to send data.
* @param size Size of data to write to trace buffer.
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
*
* @return non-NULL on success, otherwise NULL.
*/
uint8_t *esp_apptrace_buffer_get(esp_apptrace_dest_t dest, size_t size, uint32_t tmo);
/**
* @brief Indicates that the data in buffer are ready to be sent off.
* This function is a counterpart of must be preceeded by esp_apptrace_buffer_get.
*
* @param dest Indicates HW interface to send data. Should be identical to the same parameter in call to esp_apptrace_buffer_get.
* @param ptr Address of trace buffer to release. Should be the value returned by call to esp_apptrace_buffer_get.
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
esp_err_t esp_apptrace_buffer_put(esp_apptrace_dest_t dest, uint8_t *ptr, uint32_t tmo);
/**
* @brief Writes data to trace buffer.
*
* @param dest Indicates HW interface to send data.
* @param data Address of data to write to trace buffer.
* @param size Size of data to write to trace buffer.
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
esp_err_t esp_apptrace_write(esp_apptrace_dest_t dest, void *data, size_t size, uint32_t tmo);
/**
* @brief vprintf-like function to sent log messages to host via specified HW interface.
*
* @param dest Indicates HW interface to send data.
* @param fmt Address of format string.
* @param ap List of arguments.
*
* @return Number of bytes written.
*/
int esp_apptrace_vprintf_to(esp_apptrace_dest_t dest, uint32_t user_tmo, const char *fmt, va_list ap);
/**
* @brief vprintf-like function to sent log messages to host.
*
* @param fmt Address of format string.
* @param ap List of arguments.
*
* @return Number of bytes written.
*/
int esp_apptrace_vprintf(const char *fmt, va_list ap);
/**
* @brief Flushes remaining data in trace buffer to host.
*
* @param dest Indicates HW interface to flush data on.
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
esp_err_t esp_apptrace_flush(esp_apptrace_dest_t dest, uint32_t tmo);
/**
* @brief Flushes remaining data in trace buffer to host without locking internal data.
This is special version of esp_apptrace_flush which should be called from panic handler.
*
* @param dest Indicates HW interface to flush data on.
* @param min_sz Threshold for flushing data. If current filling level is above this value, data will be flushed. TRAX destinations only.
* @param tmo Timeout for operation (in us). Use ESP_APPTRACE_TMO_INFINITE to wait indefinetly.
*
* @return ESP_OK on success, otherwise \see esp_err_t
*/
esp_err_t esp_apptrace_flush_nolock(esp_apptrace_dest_t dest, uint32_t min_sz, uint32_t tmo);
#endif

View File

@ -86,6 +86,7 @@ SECTIONS
*libesp32.a:panic.o(.literal .text .literal.* .text.*)
*libesp32.a:core_dump.o(.literal .text .literal.* .text.*)
*libesp32.a:heap_alloc_caps.o(.literal .text .literal.* .text.*)
*libesp32.a:app_trace.o(.literal .text .literal.* .text.*)
*libphy.a:(.literal .text .literal.* .text.*)
*librtc.a:(.literal .text .literal.* .text.*)
*libsoc.a:(.literal .text .literal.* .text.*)
@ -116,6 +117,7 @@ SECTIONS
KEEP(*(.jcr))
*(.dram1 .dram1.*)
*libesp32.a:panic.o(.rodata .rodata.*)
*libesp32.a:app_trace.o(.rodata .rodata.*)
_data_end = ABSOLUTE(.);
. = ALIGN(4);
} >dram0_0_seg

View File

@ -38,6 +38,7 @@
#include "esp_core_dump.h"
#include "esp_spi_flash.h"
#include "esp_cache_err_int.h"
#include "esp_app_trace.h"
/*
Panic handlers; these get called when an unhandled exception occurs or the assembly-level
@ -114,6 +115,9 @@ static bool abort_called;
static __attribute__((noreturn)) inline void invoke_abort()
{
abort_called = true;
#if CONFIG_ESP32_APPTRACE_ENABLE
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
#endif
while(1) {
__asm__ ("break 0,0");
*((int*) 0) = 0;
@ -226,6 +230,9 @@ void panicHandler(XtExcFrame *frame)
}
if (esp_cpu_in_ocd_debug_mode()) {
#if CONFIG_ESP32_APPTRACE_ENABLE
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
#endif
setFirstBreakpoint(frame->pc);
return;
}
@ -248,6 +255,9 @@ void xt_unhandled_exception(XtExcFrame *frame)
panicPutStr(" at pc=");
panicPutHex(frame->pc);
panicPutStr(". Setting bp and returning..\r\n");
#if CONFIG_ESP32_APPTRACE_ENABLE
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
#endif
//Stick a hardware breakpoint on the address the handler returns to. This way, the OCD debugger
//will kick in exactly at the context the error happened.
setFirstBreakpoint(frame->pc);
@ -282,11 +292,10 @@ static void reconfigureAllWdts()
TIMERG1.wdt_wprotect = 0;
}
#if CONFIG_ESP32_PANIC_GDBSTUB || CONFIG_ESP32_PANIC_PRINT_HALT || CONFIG_ESP32_ENABLE_COREDUMP
/*
This disables all the watchdogs for when we call the gdbstub.
*/
static void disableAllWdts()
static inline void disableAllWdts()
{
TIMERG0.wdt_wprotect = TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_config0.en = 0;
@ -296,8 +305,6 @@ static void disableAllWdts()
TIMERG1.wdt_wprotect = 0;
}
#endif
static void esp_panic_wdt_start()
{
if (REG_GET_BIT(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_EN)) {
@ -422,6 +429,12 @@ static void commonErrorHandler(XtExcFrame *frame)
/* With windowed ABI backtracing is easy, let's do it. */
doBacktrace(frame);
#if CONFIG_ESP32_APPTRACE_ENABLE
disableAllWdts();
esp_apptrace_flush_nolock(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TRAX_BLOCK_SIZE*CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TRAX_THRESH/100, CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO);
reconfigureAllWdts();
#endif
#if CONFIG_ESP32_PANIC_GDBSTUB
disableAllWdts();
esp_panic_wdt_stop();

View File

@ -2,77 +2,816 @@
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include "unity.h"
#include "soc/soc.h"
#include "soc/dport_reg.h"
#include "eri.h"
#include "trax.h"
#include "driver/timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#if CONFIG_ESP32_APPTRACE_ENABLE == 1
#include "esp_app_trace.h"
#define ESP_APPTRACE_TEST_USE_PRINT_LOCK 0
#define ESP_APPTRACE_TEST_PRN_WRERR_MAX 5
#define ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH 100
#define ESP_APPTRACE_TEST_BLOCK_SIZE 1024
// TODO: move these (and same definitions in trax.c to dport_reg.h)
#define TRACEMEM_MUX_PROBLK0_APPBLK1 0
#define TRACEMEM_MUX_BLK0_ONLY 1
#define TRACEMEM_MUX_BLK1_ONLY 2
#define TRACEMEM_MUX_PROBLK1_APPBLK0 3
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"
const static char *TAG = "esp_apptrace_test";
static uint8_t* s_tracemem_blocks[] = {
(uint8_t*) 0x3FFFC000,
(uint8_t*) 0x3FFF8000
};
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
#define ESP_APPTRACE_TEST_LOG( format, ... ) \
do { \
BaseType_t ret; \
if (xPortInIsrContext()) \
ret = xSemaphoreTakeFromISR(s_print_lock, NULL); \
else \
ret = xSemaphoreTake(s_print_lock, portMAX_DELAY); \
if (ret == pdTRUE) { \
ets_printf(format, ##__VA_ARGS__); \
if (xPortInIsrContext()) \
xSemaphoreGiveFromISR(s_print_lock, NULL); \
else \
xSemaphoreGive(s_print_lock); \
} \
} while(0)
#else
#define ESP_APPTRACE_TEST_LOG( format, ... ) \
do { \
ets_printf(format, ##__VA_ARGS__); \
} while(0)
#endif
static const size_t TRACEMEM_BLOCK_SIZE = 0x4000;
#define ESP_APPTRACE_TEST_LOG_LEVEL( _L_, level, format, ... ) \
do { \
if (LOG_LOCAL_LEVEL >= level) { \
ESP_APPTRACE_TEST_LOG(LOG_FORMAT(_L_, format), esp_log_early_timestamp(), TAG, ##__VA_ARGS__); \
} \
} while(0)
#define ESP_APPTRACE_TEST_LOGE( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(E, ESP_LOG_ERROR, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_LOGW( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(W, ESP_LOG_WARN, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_LOGI( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(I, ESP_LOG_INFO, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_LOGD( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(D, ESP_LOG_DEBUG, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_LOGV( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(V, ESP_LOG_VERBOSE, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_LOGO( format, ... ) ESP_APPTRACE_TEST_LOG_LEVEL(E, ESP_LOG_NONE, format, ##__VA_ARGS__)
#define ESP_APPTRACE_TEST_WRITE(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, ESP_APPTRACE_TMO_INFINITE)
#define ESP_APPTRACE_TEST_WRITE_FROM_ISR(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 100UL)
#define ESP_APPTRACE_TEST_WRITE_NOWAIT(_b_, _s_) esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, _b_, _s_, 0)
#define ESP_APPTRACE_TEST_CPUTICKS2US(_t_) ((_t_)/(XT_CLOCK_FREQ/1000000))
typedef struct {
int block;
SemaphoreHandle_t done;
} fill_tracemem_arg_t;
uint8_t *buf;
uint32_t buf_sz;
uint8_t mask;
uint32_t period; // trace write period in us
uint32_t wr_err;
uint32_t wr_cnt;
} esp_apptrace_test_gen_data_t;
static void fill_tracemem(void* p)
typedef struct {
int group;
int id;
void (*isr_func)(void *);
esp_apptrace_test_gen_data_t data;
} esp_apptrace_test_timer_arg_t;
typedef struct {
int nowait;
int core;
int prio;
void (*task_func)(void *);
esp_apptrace_test_gen_data_t data;
volatile int stop;
SemaphoreHandle_t done;
uint32_t timers_num;
esp_apptrace_test_timer_arg_t *timers;
} esp_apptrace_test_task_arg_t;
typedef struct {
uint32_t tasks_num;
esp_apptrace_test_task_arg_t *tasks;
} esp_apptrace_test_cfg_t;
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
static SemaphoreHandle_t s_print_lock;
#endif
static uint64_t esp_apptrace_test_ts_get();
static void esp_apptrace_test_timer_init(int timer_group, int timer_idx, uint32_t period)
{
fill_tracemem_arg_t* arg = (fill_tracemem_arg_t*) p;
int coreId = xPortGetCoreID();
memset(s_tracemem_blocks[arg->block] + coreId * TRACEMEM_BLOCK_SIZE / 2,
(coreId) ? 0xba:0xab, TRACEMEM_BLOCK_SIZE / 2);
timer_config_t config;
uint64_t alarm_val = (period * (TIMER_BASE_CLK / 1000000UL)) / 2;
config.alarm_en = 1;
config.auto_reload = 1;
config.counter_dir = TIMER_COUNT_UP;
config.divider = 1;
config.intr_type = TIMER_INTR_LEVEL;
config.counter_en = TIMER_PAUSE;
/*Configure timer*/
timer_init(timer_group, timer_idx, &config);
/*Stop timer counter*/
timer_pause(timer_group, timer_idx);
/*Load counter value */
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
/*Set alarm value*/
timer_set_alarm_value(timer_group, timer_idx, alarm_val);
/*Enable timer interrupt*/
timer_enable_intr(timer_group, timer_idx);
}
static void esp_apptrace_test_timer_isr(void *arg)
{
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
*ts = (uint32_t)esp_apptrace_test_ts_get();
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
if (res != ESP_OK) {
} else {
if (0) {
ets_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
}
tim_arg->data.wr_err = 0;
}
tim_arg->data.wr_cnt++;
if (tim_arg->group == 0) {
if (tim_arg->id == 0) {
TIMERG0.int_clr_timers.t0 = 1;
TIMERG0.hw_timer[0].update = 1;
TIMERG0.hw_timer[0].config.alarm_en = 1;
} else {
TIMERG0.int_clr_timers.t1 = 1;
TIMERG0.hw_timer[1].update = 1;
TIMERG0.hw_timer[1].config.alarm_en = 1;
}
}
if (tim_arg->group == 1) {
if (tim_arg->id == 0) {
TIMERG1.int_clr_timers.t0 = 1;
TIMERG1.hw_timer[0].update = 1;
TIMERG1.hw_timer[0].config.alarm_en = 1;
} else {
TIMERG1.int_clr_timers.t1 = 1;
TIMERG1.hw_timer[1].update = 1;
TIMERG1.hw_timer[1].config.alarm_en = 1;
}
}
}
static void esp_apptrace_test_timer_isr_crash(void *arg)
{
esp_apptrace_test_timer_arg_t *tim_arg = (esp_apptrace_test_timer_arg_t *)arg;
if (tim_arg->group == 0) {
if (tim_arg->id == 0) {
TIMERG0.int_clr_timers.t0 = 1;
TIMERG0.hw_timer[0].update = 1;
TIMERG0.hw_timer[0].config.alarm_en = 1;
} else {
TIMERG0.int_clr_timers.t1 = 1;
TIMERG0.hw_timer[1].update = 1;
TIMERG0.hw_timer[1].config.alarm_en = 1;
}
}
if (tim_arg->group == 1) {
if (tim_arg->id == 0) {
TIMERG1.int_clr_timers.t0 = 1;
TIMERG1.hw_timer[0].update = 1;
TIMERG1.hw_timer[0].config.alarm_en = 1;
} else {
TIMERG1.int_clr_timers.t1 = 1;
TIMERG1.hw_timer[1].update = 1;
TIMERG1.hw_timer[1].config.alarm_en = 1;
}
}
if (tim_arg->data.wr_cnt < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH) {
uint32_t *ts = (uint32_t *)(tim_arg->data.buf + sizeof(uint32_t));
*ts = (uint32_t)esp_apptrace_test_ts_get();//xthal_get_ccount();//xTaskGetTickCount();
memset(tim_arg->data.buf + 2 * sizeof(uint32_t), tim_arg->data.wr_cnt & tim_arg->data.mask, tim_arg->data.buf_sz - 2 * sizeof(uint32_t));
int res = ESP_APPTRACE_TEST_WRITE_FROM_ISR(tim_arg->data.buf, tim_arg->data.buf_sz);
if (res != ESP_OK) {
ets_printf("tim-%d-%d: Failed to write trace %d %x!\n", tim_arg->group, tim_arg->id, res, tim_arg->data.wr_cnt & tim_arg->data.mask);
} else {
ets_printf("tim-%d-%d: Written chunk%d %d bytes, %x\n",
tim_arg->group, tim_arg->id, tim_arg->data.wr_cnt, tim_arg->data.buf_sz, tim_arg->data.wr_cnt & tim_arg->data.mask);
tim_arg->data.wr_cnt++;
}
} else {
uint32_t *ptr = 0;
*ptr = 1000;
}
}
static void esp_apptrace_dummy_task(void *p)
{
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
int res, flags = 0, i;
timer_isr_handle_t *inth = NULL;
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
ESP_APPTRACE_TEST_LOGI("%x: run dummy task (period %u us, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->timers_num);
if (arg->timers_num > 0) {
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
if (!inth) {
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
goto on_fail;
}
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
for (int i = 0; i < arg->timers_num; i++) {
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
if (res != ESP_OK) {
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
goto on_fail;
}
*(uint32_t *)arg->timers[i].data.buf = (uint32_t)inth[i] | (1 << 31);
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
res = timer_start(arg->timers[i].group, arg->timers[i].id);
if (res != ESP_OK) {
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
goto on_fail;
}
}
}
i = 0;
while (!arg->stop) {
ESP_APPTRACE_TEST_LOGD("%x: dummy task work %d.%d", xTaskGetCurrentTaskHandle(), xPortGetCoreID(), i++);
if (tmo_ticks) {
vTaskDelay(tmo_ticks);
}
}
on_fail:
if (inth) {
for (int i = 0; i < arg->timers_num; i++) {
timer_pause(arg->timers[i].group, arg->timers[i].id);
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
if (inth[i]) {
esp_intr_free(inth[i]);
}
}
vPortFree(inth);
}
xSemaphoreGive(arg->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
TEST_CASE("both CPUs can write to trace block 0", "[trace][ignore]")
static void esp_apptrace_test_task(void *p)
{
// Configure block 1 as trace memory, enable access via both CPUs
WRITE_PERI_REG(DPORT_PRO_TRACEMEM_ENA_REG, DPORT_PRO_TRACEMEM_ENA_M);
WRITE_PERI_REG(DPORT_APP_TRACEMEM_ENA_REG, DPORT_APP_TRACEMEM_ENA_M);
WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, TRACEMEM_MUX_BLK1_ONLY);
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
int res, flags = 0;
timer_isr_handle_t *inth = NULL;
TickType_t tmo_ticks = arg->data.period / (1000 * portTICK_PERIOD_MS);
// Stop trace, if any (on the current CPU)
eri_write(ERI_TRAX_TRAXCTRL, eri_read(ERI_TRAX_TRAXCTRL) | TRAXCTRL_TRSTP);
eri_write(ERI_TRAX_TRAXCTRL, TRAXCTRL_TMEN);
// TODO: make sure trace is not running on the other CPU
ESP_APPTRACE_TEST_LOGI("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
// fill two halves of the first trace mem block
fill_tracemem_arg_t arg1 = {
.block = 0,
.done = xSemaphoreCreateBinary()
};
if (arg->timers_num > 0) {
inth = pvPortMalloc(arg->timers_num * sizeof(timer_isr_handle_t));
if (!inth) {
ESP_APPTRACE_TEST_LOGE("Failed to alloc timer ISR handles!");
goto on_fail;
}
memset(inth, 0, arg->timers_num * sizeof(timer_isr_handle_t));
for (int i = 0; i < arg->timers_num; i++) {
esp_apptrace_test_timer_init(arg->timers[i].group, arg->timers[i].id, arg->timers[i].data.period);
res = timer_isr_register(arg->timers[i].group, arg->timers[i].id, arg->timers[i].isr_func, &arg->timers[i], flags, &inth[i]);
if (res != ESP_OK) {
ESP_APPTRACE_TEST_LOGE("Failed to timer_isr_register (%d)!", res);
goto on_fail;
}
*(uint32_t *)arg->timers[i].data.buf = ((uint32_t)inth[i]) | (1 << 31) | (xPortGetCoreID() ? 0x1 : 0);
ESP_APPTRACE_TEST_LOGI("%x: start timer %x period %u us", xTaskGetCurrentTaskHandle(), inth[i], arg->timers[i].data.period);
res = timer_start(arg->timers[i].group, arg->timers[i].id);
if (res != ESP_OK) {
ESP_APPTRACE_TEST_LOGE("Failed to timer_start (%d)!", res);
goto on_fail;
}
}
}
fill_tracemem_arg_t arg2 = {
.block = 0,
.done = xSemaphoreCreateBinary()
};
xTaskCreatePinnedToCore(&fill_tracemem, "fill1", 2048, &arg1, 3, NULL, 0);
xTaskCreatePinnedToCore(&fill_tracemem, "fill2", 2048, &arg2, 3, NULL, 1);
xSemaphoreTake(arg1.done, 1);
xSemaphoreTake(arg2.done, 1);
vSemaphoreDelete(arg1.done);
vSemaphoreDelete(arg2.done);
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle() | (xPortGetCoreID() ? 0x1 : 0);
arg->data.wr_cnt = 0;
arg->data.wr_err = 0;
while (!arg->stop) {
uint32_t *ts = (uint32_t *)(arg->data.buf + sizeof(uint32_t));
*ts = (uint32_t)esp_apptrace_test_ts_get();
memset(arg->data.buf + 2 * sizeof(uint32_t), arg->data.wr_cnt & arg->data.mask, arg->data.buf_sz - 2 * sizeof(uint32_t));
if (arg->nowait) {
res = ESP_APPTRACE_TEST_WRITE_NOWAIT(arg->data.buf, arg->data.buf_sz);
} else {
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
}
if (res) {
if (arg->data.wr_err++ < ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
if (arg->data.wr_err == ESP_APPTRACE_TEST_PRN_WRERR_MAX) {
ESP_APPTRACE_TEST_LOGE("\n");
}
}
} else {
if (0) {
ESP_APPTRACE_TEST_LOGD("%x:%x: Written chunk%d %d bytes, %x", xTaskGetCurrentTaskHandle(), *ts, arg->data.wr_cnt, arg->data.buf_sz, arg->data.wr_cnt & arg->data.mask);
}
arg->data.wr_err = 0;
}
arg->data.wr_cnt++;
if (tmo_ticks) {
vTaskDelay(tmo_ticks);
}
}
// Block 0 is filled with data — configure it as trace memory so that it is accessible via TRAX module
WRITE_PERI_REG(DPORT_TRACEMEM_MUX_MODE_REG, TRACEMEM_MUX_BLK0_ONLY);
// Block 1 can now be filled with data
on_fail:
if (inth) {
for (int i = 0; i < arg->timers_num; i++) {
timer_pause(arg->timers[i].group, arg->timers[i].id);
timer_disable_intr(arg->timers[i].group, arg->timers[i].id);
if (inth[i]) {
esp_intr_free(inth[i]);
}
}
vPortFree(inth);
}
xSemaphoreGive(arg->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
static void esp_apptrace_test_task_crash(void *p)
{
esp_apptrace_test_task_arg_t *arg = (esp_apptrace_test_task_arg_t *) p;
int res, i;
ESP_APPTRACE_TEST_LOGE("%x: run (period %u us, stamp mask %x, %u timers)", xTaskGetCurrentTaskHandle(), arg->data.period, arg->data.mask, arg->timers_num);
arg->data.wr_cnt = 0;
*(uint32_t *)arg->data.buf = (uint32_t)xTaskGetCurrentTaskHandle();
for (i = 0; i < ESP_APPTRACE_TEST_BLOCKS_BEFORE_CRASH; i++) {
uint32_t *ts = (uint32_t *)(arg->data.buf + sizeof(uint32_t));
*ts = (uint32_t)esp_apptrace_test_ts_get();
memset(arg->data.buf + sizeof(uint32_t), arg->data.wr_cnt & arg->data.mask, arg->data.buf_sz - sizeof(uint32_t));
res = ESP_APPTRACE_TEST_WRITE(arg->data.buf, arg->data.buf_sz);
if (res) {
ESP_APPTRACE_TEST_LOGE("%x: Failed to write trace %d %x!", xTaskGetCurrentTaskHandle(), res, arg->data.wr_cnt & arg->data.mask);
} else {
ESP_APPTRACE_TEST_LOGD("%x: Written chunk%d %d bytes, %x", xTaskGetCurrentTaskHandle(), arg->data.wr_cnt, arg->data.buf_sz, arg->data.wr_cnt & arg->data.mask);
}
arg->data.wr_cnt++;
}
vTaskDelay(500);
uint32_t *ptr = 0;
*ptr = 1000;
xSemaphoreGive(arg->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
static int s_ts_timer_group, s_ts_timer_idx;
static uint64_t esp_apptrace_test_ts_get()
{
uint64_t ts = 0;
timer_get_counter_value(s_ts_timer_group, s_ts_timer_idx, &ts);
return ts;
}
static void esp_apptrace_test_ts_init(int timer_group, int timer_idx)
{
timer_config_t config;
//uint64_t alarm_val = period * (TIMER_BASE_CLK / 1000000UL);
ESP_APPTRACE_TEST_LOGI("Use timer%d.%d for TS", timer_group, timer_idx);
s_ts_timer_group = timer_group;
s_ts_timer_idx = timer_idx;
config.alarm_en = 0;
config.auto_reload = 0;
config.counter_dir = TIMER_COUNT_UP;
config.divider = 1;
config.counter_en = 0;
/*Configure timer*/
timer_init(timer_group, timer_idx, &config);
/*Load counter value */
timer_set_counter_value(timer_group, timer_idx, 0x00000000ULL);
/*Enable timer interrupt*/
timer_start(timer_group, timer_idx);
}
static void esp_apptrace_test_ts_cleanup()
{
timer_config_t config;
config.alarm_en = 0;
config.auto_reload = 0;
config.counter_dir = TIMER_COUNT_UP;
config.divider = 1;
config.counter_en = 0;
/*Configure timer*/
timer_init(s_ts_timer_group, s_ts_timer_idx, &config);
}
static void esp_apptrace_test(esp_apptrace_test_cfg_t *test_cfg)
{
int i, k;
int tims_in_use[TIMER_GROUP_MAX][TIMER_MAX] = {{0, 0}, {0, 0}};
esp_apptrace_test_task_arg_t dummy_task_arg[1];
memset(dummy_task_arg, 0, sizeof(dummy_task_arg));
dummy_task_arg[0].core = 0;
dummy_task_arg[0].prio = 3;
dummy_task_arg[0].task_func = esp_apptrace_test_task_crash;
dummy_task_arg[0].data.buf = NULL;
dummy_task_arg[0].data.buf_sz = 0;
dummy_task_arg[0].data.period = 500000;
dummy_task_arg[0].timers_num = 0;
dummy_task_arg[0].timers = NULL;
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
s_print_lock = xSemaphoreCreateBinary();
if (!s_print_lock) {
ets_printf("%s: Failed to create print lock!", TAG);
return;
}
xSemaphoreGive(s_print_lock);
#else
#endif
for (i = 0; i < test_cfg->tasks_num; i++) {
test_cfg->tasks[i].data.mask = 0xFF;
test_cfg->tasks[i].stop = 0;
test_cfg->tasks[i].done = xSemaphoreCreateBinary();
if (!test_cfg->tasks[i].done) {
ESP_APPTRACE_TEST_LOGE("Failed to create task completion semaphore!");
goto on_fail;
}
for (k = 0; k < test_cfg->tasks[i].timers_num; k++) {
test_cfg->tasks[i].timers[k].data.mask = 0xFF;
tims_in_use[test_cfg->tasks[i].timers[k].group][test_cfg->tasks[i].timers[k].id] = 1;
}
}
int found = 0;
for (i = 0; i < TIMER_GROUP_MAX; i++) {
for (k = 0; k < TIMER_MAX; k++) {
if (!tims_in_use[i][k]) {
ESP_APPTRACE_TEST_LOGD("Found timer%d.%d", i, k);
found = 1;
break;
}
}
if (found) {
break;
}
}
if (!found) {
ESP_APPTRACE_TEST_LOGE("No free timer for TS!");
goto on_fail;
}
esp_apptrace_test_ts_init(i, k);
for (int i = 0; i < test_cfg->tasks_num; i++) {
char name[30];
TaskHandle_t thnd;
sprintf(name, "apptrace_test%d", i);
xTaskCreatePinnedToCore(test_cfg->tasks[i].task_func, name, 2048, &test_cfg->tasks[i], test_cfg->tasks[i].prio, &thnd, test_cfg->tasks[i].core);
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
}
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy0", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 0);
xTaskCreatePinnedToCore(esp_apptrace_dummy_task, "dummy1", 2048, &dummy_task_arg[0], dummy_task_arg[0].prio, NULL, 1);
for (int i = 0; i < test_cfg->tasks_num; i++) {
//arg1.stop = 1;
xSemaphoreTake(test_cfg->tasks[i].done, portMAX_DELAY);
}
on_fail:
for (int i = 0; i < test_cfg->tasks_num; i++) {
if (test_cfg->tasks[i].done) {
vSemaphoreDelete(test_cfg->tasks[i].done);
}
}
esp_apptrace_test_ts_cleanup();
#if ESP_APPTRACE_TEST_USE_PRINT_LOCK == 1
vSemaphoreDelete(s_print_lock);
#else
#endif
}
static esp_apptrace_test_task_arg_t s_test_tasks[4];
static esp_apptrace_test_timer_arg_t s_test_timers[2];
static uint8_t s_bufs[6][ESP_APPTRACE_TEST_BLOCK_SIZE];
TEST_CASE("App trace test (1 task + 1 crashed timer ISR @ 1 core)", "[trace][ignore]")
{
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 1,
.tasks = s_test_tasks,
};
memset(s_test_timers, 0, sizeof(s_test_timers));
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_timers[0].group = TIMER_GROUP_0;
s_test_timers[0].id = TIMER_0;
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr_crash;
s_test_timers[0].data.buf = s_bufs[0];
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_timers[0].data.period = 1000;
s_test_tasks[0].core = 0;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_dummy_task;
s_test_tasks[0].data.buf = NULL;
s_test_tasks[0].data.buf_sz = 0;
s_test_tasks[0].data.period = 1000000;
s_test_tasks[0].timers_num = 1;
s_test_tasks[0].timers = s_test_timers;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (1 crashed task)", "[trace][ignore]")
{
esp_apptrace_test_task_arg_t s_test_tasks[1];
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 1,
.tasks = s_test_tasks,
};
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_tasks[0].core = 0;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_test_task_crash;
s_test_tasks[0].data.buf = s_bufs[0];
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_tasks[0].data.period = 6000;
s_test_tasks[0].timers_num = 0;
s_test_tasks[0].timers = NULL;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (2 tasks + 1 timer @ each core", "[trace][ignore]")
{
int ntask = 0;
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 4,
.tasks = s_test_tasks,
};
memset(s_test_tasks, 0, sizeof(s_test_tasks));
memset(s_test_timers, 0, sizeof(s_test_timers));
s_test_timers[0].group = TIMER_GROUP_0;
s_test_timers[0].id = TIMER_0;
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
s_test_timers[0].data.buf = s_bufs[0];
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_timers[0].data.period = 150;
s_test_timers[1].group = TIMER_GROUP_1;
s_test_timers[1].id = TIMER_0;
s_test_timers[1].isr_func = esp_apptrace_test_timer_isr;
s_test_timers[1].data.buf = s_bufs[1];
s_test_timers[1].data.buf_sz = sizeof(s_bufs[1]);
s_test_timers[1].data.period = 150;
s_test_tasks[ntask].core = 0;
s_test_tasks[ntask].prio = 4;
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
s_test_tasks[ntask].data.buf = s_bufs[2];
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[2]);
s_test_tasks[ntask].data.period = 1000;
s_test_tasks[ntask].timers_num = 1;
s_test_tasks[ntask].timers = &s_test_timers[0];
ntask++;
s_test_tasks[ntask].core = 0;
s_test_tasks[ntask].prio = 3;
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
s_test_tasks[ntask].data.buf = s_bufs[3];
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[3]);
s_test_tasks[ntask].data.period = 0;
s_test_tasks[ntask].timers_num = 0;
s_test_tasks[ntask].timers = NULL;
ntask++;
s_test_tasks[ntask].core = 1;
s_test_tasks[ntask].prio = 4;
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
s_test_tasks[ntask].data.buf = s_bufs[4];
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[4]);
s_test_tasks[ntask].data.period = 1000;
s_test_tasks[ntask].timers_num = 1;
s_test_tasks[ntask].timers = &s_test_timers[1];
ntask++;
s_test_tasks[ntask].core = 1;
s_test_tasks[ntask].prio = 3;
s_test_tasks[ntask].task_func = esp_apptrace_test_task;
s_test_tasks[ntask].data.buf = s_bufs[5];
s_test_tasks[ntask].data.buf_sz = sizeof(s_bufs[5]);
s_test_tasks[ntask].data.period = 0;
s_test_tasks[ntask].timers_num = 0;
s_test_tasks[ntask].timers = NULL;
ntask++;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (1 task + 1 timer @ 1 core)", "[trace][ignore]")
{
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 1,
.tasks = s_test_tasks,
};
memset(s_test_timers, 0, sizeof(s_test_timers));
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_timers[0].group = TIMER_GROUP_0;
s_test_timers[0].id = TIMER_0;
s_test_timers[0].isr_func = esp_apptrace_test_timer_isr;
s_test_timers[0].data.buf = s_bufs[0];
s_test_timers[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_timers[0].data.period = 150;
s_test_tasks[0].core = 0;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_test_task;
s_test_tasks[0].data.buf = s_bufs[1];
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[1]);
s_test_tasks[0].data.period = 0;
s_test_tasks[0].timers_num = 1;
s_test_tasks[0].timers = s_test_timers;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (2 tasks (nowait): 1 @ each core)", "[trace][ignore]")
{
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 2,
.tasks = s_test_tasks,
};
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_tasks[0].nowait = 1;
s_test_tasks[0].core = 0;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_test_task;
s_test_tasks[0].data.buf = s_bufs[0];
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_tasks[0].data.period = 6700;
s_test_tasks[0].timers_num = 0;
s_test_tasks[0].timers = NULL;
s_test_tasks[1].nowait = 1;
s_test_tasks[1].core = 1;
s_test_tasks[1].prio = 3;
s_test_tasks[1].task_func = esp_apptrace_test_task;
s_test_tasks[1].data.buf = s_bufs[1];
s_test_tasks[1].data.buf_sz = sizeof(s_bufs[1]);
s_test_tasks[1].data.period = 6700;
s_test_tasks[1].timers_num = 0;
s_test_tasks[1].timers = NULL;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (2 tasks: 1 @ each core)", "[trace][ignore]")
{
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 2,
.tasks = s_test_tasks,
};
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_tasks[0].core = 0;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_test_task;
s_test_tasks[0].data.buf = s_bufs[0];
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_tasks[0].data.period = 0;
s_test_tasks[0].timers_num = 0;
s_test_tasks[0].timers = NULL;
s_test_tasks[1].core = 1;
s_test_tasks[1].prio = 3;
s_test_tasks[1].task_func = esp_apptrace_test_task;
s_test_tasks[1].data.buf = s_bufs[1];
s_test_tasks[1].data.buf_sz = sizeof(s_bufs[1]);
s_test_tasks[1].data.period = 0;
s_test_tasks[1].timers_num = 0;
s_test_tasks[1].timers = NULL;
esp_apptrace_test(&test_cfg);
}
TEST_CASE("App trace test (1 task)", "[trace][ignore]")
{
esp_apptrace_test_cfg_t test_cfg = {
.tasks_num = 1,
.tasks = s_test_tasks,
};
memset(s_test_tasks, 0, sizeof(s_test_tasks));
s_test_tasks[0].core = 1;
s_test_tasks[0].prio = 3;
s_test_tasks[0].task_func = esp_apptrace_test_task;
s_test_tasks[0].data.buf = s_bufs[0];
s_test_tasks[0].data.buf_sz = sizeof(s_bufs[0]);
s_test_tasks[0].data.period = 0;
s_test_tasks[0].timers_num = 0;
s_test_tasks[0].timers = NULL;
esp_apptrace_test(&test_cfg);
}
static int esp_logtrace_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int ret = esp_apptrace_vprintf_to(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TMO_INFINITE, fmt, ap);
va_end(ap);
return ret;
}
typedef struct {
SemaphoreHandle_t done;
} esp_logtrace_task_t;
static void esp_logtrace_task(void *p)
{
esp_logtrace_task_t *arg = (esp_logtrace_task_t *) p;
ESP_APPTRACE_TEST_LOGI("%x: run log test task", xTaskGetCurrentTaskHandle());
int i = 0;
while (1) {
esp_logtrace_printf("sample print %lx %hx %c\n", 2 * i + 0x10, 2 * i + 0x20, (2 * i + 0x30) & 0xFF);
esp_logtrace_printf("sample print %lx %hx %c %lu %hu %d %d %d %d\n", i, i + 0x10, (i + 0x20) & 0xFF, i + 0x30, i + 0x40, i + 0x50, i + 0x60, i + 0x70, i + 0x80);
ESP_LOGI(TAG, "%p: sample print 1", xTaskGetCurrentTaskHandle());
ESP_LOGI(TAG, "%p: sample print 2 %u", xTaskGetCurrentTaskHandle(), (unsigned)i);
ESP_LOGI(TAG, "%p: sample print 4 %c", xTaskGetCurrentTaskHandle(), ((i & 0xFF) % 95) + 32);
ESP_LOGI(TAG, "%p: sample print 5 %f", xTaskGetCurrentTaskHandle(), 1.0);
ESP_LOGI(TAG, "%p: sample print 6 %f", xTaskGetCurrentTaskHandle(), 3.45);
ESP_LOGI(TAG, "%p: logtrace task work %d.%d", xTaskGetCurrentTaskHandle(), xPortGetCoreID(), i);
if (++i == 10000) {
break;
}
}
esp_err_t ret = esp_apptrace_flush(ESP_APPTRACE_DEST_TRAX, ESP_APPTRACE_TMO_INFINITE);
if (ret != ESP_OK) {
ESP_APPTRACE_TEST_LOGE("Failed to flush printf buf (%d)!", ret);
}
ESP_APPTRACE_TEST_LOGI("%x: finished", xTaskGetCurrentTaskHandle());
xSemaphoreGive(arg->done);
vTaskDelay(1);
vTaskDelete(NULL);
}
TEST_CASE("Log trace test (1 task)", "[trace][ignore]")
{
TaskHandle_t thnd;
esp_logtrace_task_t arg1 = {
.done = xSemaphoreCreateBinary(),
};
esp_logtrace_task_t arg2 = {
.done = xSemaphoreCreateBinary(),
};
xTaskCreatePinnedToCore(esp_logtrace_task, "logtrace0", 2048, &arg1, 3, &thnd, 0);
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
xTaskCreatePinnedToCore(esp_logtrace_task, "logtrace1", 2048, &arg2, 3, &thnd, 1);
ESP_APPTRACE_TEST_LOGI("Created task %x", thnd);
xSemaphoreTake(arg1.done, portMAX_DELAY);
vSemaphoreDelete(arg1.done);
xSemaphoreTake(arg2.done, portMAX_DELAY);
vSemaphoreDelete(arg2.done);
}
#endif

View File

@ -255,7 +255,6 @@ void vPortStoreTaskMPUSettings( xMPU_SETTINGS *xMPUSettings, const struct xMEMOR
}
#endif
/*
* Returns true if the current core is in ISR context; low prio ISR, med prio ISR or timer tick ISR. High prio ISRs
* aren't detected here, but they normally cannot call C code, so that should not be an issue anyway.

View File

@ -59,3 +59,136 @@ To configure logging output per module at runtime, add calls to ``esp_log_level_
esp_log_level_set("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack
esp_log_level_set("dhcpc", ESP_LOG_INFO); // enable INFO logs from DHCP client
Logging to Host via JTAG
^^^^^^^^^^^^^^^^^^^^^^^^
By default logging library uses vprintf-like function to write formatted output to dedicated UART. In general it invloves the following steps:
1. Format string is parsed to obtain type of each argument.
2. According to its type every argument is converted to string representation.
3. Format string combined with converted arguments is sent to UART.
Though implementation of vprintf-like function can be optimised to a certain level, all steps above have to be peformed in any case and every step takes some time (especially item 3). So it is frequent situation when addition of extra logging to the program to diagnose some problem changes its behaviour and problem dissapears or in the worst cases program can not work normally at all and ends up with an error or even hangs.
Possible ways to overcome this problem are to use faster UART bitrates (or another faster interface) and/or move string formatting procedure to the host.
ESP IDF has `Application Tracing` feature which allows to sent arbitrary application data to host via JTAG. This feature can also be used to transfer log information to host using ``esp_apptrace_vprintf`` function. This function does not perform full parsing of the format string and arguments, instead it just calculates number of arguments passed and sends them along with the format string address to the host. On the host log data are processed and printed out by a special Python script.
Config Options and Dependencies
"""""""""""""""""""""""""""""""
Using of the feature depends on two components:
1. Host side: Application tracing is done over JTAG, so it needs OpenOCD to be set up and running on host machine. For instructions how to set it up, please, see :idf:`OpenOCD setup for ESP32` section for details. **NOTE:** `in order to achieve higher data rates you may need to modify JTAG adapter working frequency in OpenOCD config file. Maximum tested stable speed is 26MHz, so you need to have the following statement in your configuration file` ``adapter_khz 26000`` `instead of default` ``adapter_khz 200``. `Actually maximum stable JTAG frequency can depend on host system configuration.`
2. Target side: Application tracing functionality can be enabled by ``CONFIG_ESP32_APPTRACE_ENABLE`` macro via menuconfig. This option enables the module and makes ``esp_apptrace_vprintf`` available for users.
Limitations
"""""""""""
Curent implmentation of logging over JTAG has several limitations:
1. Tracing from ``ESP_EARLY_LOGx`` macros is not supported.
2. No support for printf arguments which size exceeds 4 bytes (e.g. ``double`` and ``uint64_t``).
3. Only strings from .rodata section are supported as format strings and arguments.
4. Maximum number of printf arguments is 256.
How To Use It
"""""""""""""
To use logging via JTAG user needs to perform the following steps:
1. On target side special vprintf-like function needs to be installed. As it was mentioned earlier this function is ``esp_apptrace_vprintf``. It sends log data to the host via JTAG. Example code is shown below.
.. code-block:: c
#include "esp_app_trace.h"
...
void app_main()
{
// set log vprintf handler
esp_log_set_vprintf(esp_apptrace_vprintf);
...
// user code using ESP_LOGx starts here
// all data passed to ESP_LOGx are sent to host
...
// restore log vprintf handler
esp_log_set_vprintf(vprintf);
// flush last data to host
esp_apptrace_flush(ESP_APPTRACE_DEST_TRAX, 100000 /*tmo in us*/);
ESP_LOGI(TAG, "Tracing is finished."); // this will be printed out to UART
while (1);
}
2. Build the program image and download it to target as described in :idf:`Developing With the ESP-IDF` section.
3. Run OpenOCD (see :idf:`OpenOCD setup for ESP32` section).
4. Connect to OpenOCD telnet server. On Linux it can be done using the following command in terminal ``telnet <oocd_host> 4444``. If telnet session is opened on the same machine which runs OpenOCD you can use `localhost` as `<oocd_host>` in the command.
5. Run the following command in OpenOCD telnet session: ``esp108 apptrace start /path/to/trace/file -1 -1 0 0 1``. This command will wait for board reset and transfer tracing data at the highest possible rate.
6. Reset the board. Logging to host will start automatically.
7. ``esp108 apptrace`` command with given arguments will never return (see other command options below), so you must stop it manually by resetting the board or pressing CTRL+C in OpenOCD window (not one with the telnet session).
8. Reset board or press CTRL+C in OpenOCD window (not one with the telnet session) when tracing is completed (for the example code above after the message `"Tracing is finished."` appears on UART).
9. To print out collected log records run the following command in terminal: ``$IDF_PATH/tools/esp_app_trace/logtrace_proc.py /path/to/trace/file /path/to/program/elf/file``.
OpenOCD Application Tracing Command Options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command usage:
``esp108 apptrace [start <outfile> [options] | [stop] | [status] | [dump <outfile>]``
Sub-commands:
* ``start``. Start tracing (continuous streaming).
* ``stop``. Stop tracing.
* ``status``. Get tracing status.
* ``dump``. Dump as much data as possible without waiting for trace memory block switch (post-mortem dump).
Start command syntax:
``start <outfile> [trace_size [stop_tmo [skip_size [poll_period [wait4halt]]]]]``
.. list-table::
:widths: 20 80
:header-rows: 1
* - Argument
- Description
* - outfile
- Path to log trace file to save data
* - trace_size
- Maximum size of data to collect (in bytes). Tracing is stopped after specified amount of data is received. By default -1 (trace size stop trigger is disabled).
* - stop_tmo
- Idle timeout (in ms). Tracing is stopped if there is no data for specified period of time. By default 10 s (-1 to disable this stop trigger).
* - skip_size
- Number of bytes to skip at the start. By default 0.
* - poll_period
- Data polling period (in ms). If greater then 0 then command runs in non-blocking mode, otherwise command line will not be avalable until tracing is stopped. By default 1 ms.
* - wait4halt
- If 0 start tracing immediately, otherwise command waits for the target to be halted (after reset, by breakpoint etc) and then automatically resumes it and starts tracing. By default 0.
Log Trace Processor Command Options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Command usage:
``logtrace_proc.py [-h] [--no-errors] <trace_file> <elf_file>``
Positional arguments:
.. list-table::
:widths: 20 80
:header-rows: 1
* - Argument
- Description
* - trace_file
- Path to log trace file
* - elf_file
- Path to program ELF file
Optional arguments:
.. list-table::
:widths: 20 80
:header-rows: 1
* - Argument
- Description
* - -h, --help
- show this help message and exit
* - --no-errors, -n
- Do not print errors

View File

@ -0,0 +1,124 @@
#!/usr/bin/env python
#
import argparse
import struct
import sys
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def main():
ESP32_TRACE_BLOCK_HDR_SZ = 8
ESP32_TRACE_BLOCK_TASK_IDX = 0
ESP32_TRACE_BLOCK_TS_IDX = 1
ESP32_TRACE_BLOCK_DATA_IDX = 2
parser = argparse.ArgumentParser(description='ESP32 App Trace Parse Tool')
parser.add_argument('file', help='Path to app trace file', type=str)
parser.add_argument('--print-tasks', '-p', help='Print tasks', action='store_true')
parser.add_argument('--print-details', '-d', help='Print detailed stats', action='store_true')
parser.add_argument('--no-errors', '-n', help='Do not print errors', action='store_true')
parser.add_argument('--block-len', '-b', help='Block length', type=int, default=1024)
args = parser.parse_args()
print "===================================================================="
try:
ftrc = open(args.file, 'rb')
except IOError as e:
print "Failed to open trace file (%s)!" % e
sys.exit(2)
passed = True
off = 0
data_stats = {}
last_ts = None
tot_discont = 0
while True:
#ftrc.seek(off)
task = None
ts = 0
trc_buf = ftrc.read(args.block_len)
if len(trc_buf) == 0:
# print 'EOF'
break
trc_data = struct.unpack('<LL%sB' % (len(trc_buf) - ESP32_TRACE_BLOCK_HDR_SZ), trc_buf)
if len(trc_data):
# print "%x %x, len %d" % (trc_data[0], trc_data[1], len(trc_data) - 2)
# print trc_data[2:]
# sys.exit(0)
task = trc_data[ESP32_TRACE_BLOCK_TASK_IDX]
ts = trc_data[ESP32_TRACE_BLOCK_TS_IDX]
# print ts
if last_ts and last_ts >= ts:
# print "Global TS discontinuity %x -> %x, task %x, stamp %x at %x" % (last_ts, ts, task, data_stats[task]['stamp'], off)
if args.print_details:
print "Global TS discontinuity %x -> %x, task %x at %x" % (last_ts, ts, task, off)
# tot_discont += 1
# passed = False
last_ts = ts
if not task in data_stats:
print "%x: NEW TASK" % task
data_stats[task] = {'stamp' : trc_data[ESP32_TRACE_BLOCK_DATA_IDX], 'last_ts' : ts, 'count' : 1, 'discont_offs' : [], 'inv_stamps_offs' : []}
else:
if data_stats[task]['last_ts'] == ts:
print "Task TS discontinuity %x -> %x, task %x, stamp %x at %x" % (last_ts, ts, task, data_stats[task]['stamp'], off)
data_stats[task]['discont_offs'].append(off)
tot_discont += 1
passed = False
data_stats[task]['last_ts'] = ts
data_stats[task]['count'] += 1
if len(trc_data) > ESP32_TRACE_BLOCK_DATA_IDX:
# print "DATA = %x %x %x %x" % (trc_data[-4], trc_data[-3], trc_data[-2], trc_data[-1])
if args.print_tasks:
print "Task[%d] %x, ts %08x, stamp %x" % (off/args.block_len, task, ts, trc_data[ESP32_TRACE_BLOCK_DATA_IDX])
else:
print "%x: NO DATA" % task
else:
print "Failed to unpack data!"
sys.exit(2)
# check data
for i in range(ESP32_TRACE_BLOCK_DATA_IDX, len(trc_data)):
if trc_data[i] != data_stats[task]['stamp']:
if not args.no_errors:
print "Invalid stamp %x->%x at %x, task %x" % (data_stats[task]['stamp'], trc_data[i], off + ESP32_TRACE_BLOCK_HDR_SZ + i, task)
passed = False
data_stats[task]['stamp'] = trc_data[i]
data_stats[task]['inv_stamps_offs'].append(off)
# break
if len(trc_buf) < args.block_len:
print 'Last block (not full)'
break
if data_stats[task]['stamp'] != None:
data_stats[task]['stamp'] = (data_stats[task]['stamp'] + 1) & 0xFF
# print "stamp=%x" % data_stats[task][ESP32_TRACE_STAMP_IDX]
off += args.block_len
ftrc.close()
print "===================================================================="
print "Trace size %d bytes, discont %d\n" % (off, tot_discont)
for t in data_stats:
print "Task %x. Total count %d. Inv stamps %d. TS Discontinuities %d." % (t, data_stats[t]['count'], len(data_stats[t]['inv_stamps_offs']), len(data_stats[t]['discont_offs']))
if args.print_details:
print 'Invalid stamps offs: [{}]'.format(', '.join(hex(x) for x in data_stats[t]['inv_stamps_offs']))
print 'TS Discontinuities offs: [{}]'.format(', '.join(hex(x) for x in data_stats[t]['discont_offs']))
print "\n"
if passed:
print "Data - OK"
else:
print "Data - FAILED!"
if __name__ == '__main__':
main()

View File

@ -0,0 +1,163 @@
#!/usr/bin/env python
#
import argparse
import struct
import sys
import pylibelf as elf
import pylibelf.util as elfutil
import pylibelf.iterators as elfiter
import pylibelf.constants as elfconst
from ctypes import *
class ESPLogTraceParserError(RuntimeError):
def __init__(self, message):
RuntimeError.__init__(self, message)
class ESPLogTraceRecord(object):
def __init__(self, fmt_addr, log_args):
super(ESPLogTraceRecord, self).__init__()
self.fmt_addr = fmt_addr
self.args = log_args
def __repr__(self):
return "fmt_addr = 0x%x, args = %d/%s" % (self.fmt_addr, len(self.args), self.args)
def logtrace_parse(fname):
ESP32_LOGTRACE_HDR_FMT = '<BL'
ESP32_LOGTRACE_HDR_SZ = struct.calcsize(ESP32_LOGTRACE_HDR_FMT)
recs = []
try:
ftrc = open(fname, 'rb')
except OSError as e:
raise ESPLogTraceParserError("Failed to open trace file (%s)!" % e)
# data_ok = True
while True:
# read args num and format str addr
try:
trc_buf = ftrc.read(ESP32_LOGTRACE_HDR_SZ)
except IOError as e:
raise ESPLogTraceParserError("Failed to read log record header (%s)!" % e)
if len(trc_buf) < ESP32_LOGTRACE_HDR_SZ:
# print "EOF"
if len(trc_buf) > 0:
print "Unprocessed %d bytes of log record header!" % len(trc_buf)
# data_ok = False
break
try:
nargs,fmt_addr = struct.unpack(ESP32_LOGTRACE_HDR_FMT, trc_buf)
except struct.error as e:
raise ESPLogTraceParserError("Failed to unpack log record header (%s)!" % e)
# read args
args_sz = struct.calcsize('<%sL' % nargs)
try:
trc_buf = ftrc.read(args_sz)
except IOError as e:
raise ESPLogTraceParserError("Failed to read log record args (%s)!" % e)
if len(trc_buf) < args_sz:
# print "EOF"
if len(trc_buf) > 0:
print "Unprocessed %d bytes of log record args!" % len(trc_buf)
# data_ok = False
break
try:
log_args = struct.unpack('<%sL' % nargs, trc_buf)
except struct.error as e:
raise ESPLogTraceParserError("Failed to unpack log record args (%s)!" % e)
# print log_args
recs.append(ESPLogTraceRecord(fmt_addr, list(log_args)))
ftrc.close()
# sorted(recs, key=lambda rec: rec.fmt_addr)
return recs
def logtrace_get_str_from_elf(felf, str_addr):
tgt_str = ""
for sect in elfiter.sections(felf):
hdr = elfutil.section_hdr(felf, sect)
if hdr.sh_addr == 0 or hdr.sh_type != elfconst.SHT_PROGBITS:
continue
if str_addr < hdr.sh_addr or str_addr >= hdr.sh_addr + hdr.sh_size:
continue
# print "Found SECT: %x..%x @ %x" % (hdr.sh_addr, hdr.sh_addr + hdr.sh_size, str_addr - hdr.sh_addr)
sec_data = elfiter.getOnlyData(sect).contents
buf = cast(sec_data.d_buf, POINTER(c_char))
for i in range(str_addr - hdr.sh_addr, hdr.sh_size):
if buf[i] == "\0":
break
tgt_str += buf[i]
if len(tgt_str) > 0:
return tgt_str
return None
def logtrace_formated_print(recs, elfname, no_err):
try:
felf = elfutil.open_elf(elfname)
except OSError as e:
raise ESPLogTraceParserError("Failed to open ELF file (%s)!" % e)
for lrec in recs:
fmt_str = logtrace_get_str_from_elf(felf, lrec.fmt_addr)
i = 0
prcnt_idx = 0
while i < len(lrec.args):
prcnt_idx = fmt_str.find('%', prcnt_idx, -2) # TODO: check str ending with %
if prcnt_idx == -1:
break
prcnt_idx += 1 # goto next char
if fmt_str[prcnt_idx] == 's':
# find string
arg_str = logtrace_get_str_from_elf(felf, lrec.args[i])
if arg_str:
lrec.args[i] = arg_str
i += 1
# print "\nFmt = {%s}, args = %d/%s" % lrec
fmt_str = fmt_str.replace('%p', '%x')
# print "=====> " + fmt_str % lrec.args
try:
print fmt_str % tuple(lrec.args),
# print ".",
pass
except Exception as e:
if not no_err:
print "Print error (%s)" % e
print "\nFmt = {%s}, args = %d/%s" % (fmt_str, len(lrec.args), lrec.args)
elf.elf_end(felf)
def main():
parser = argparse.ArgumentParser(description='ESP32 Log Trace Parsing Tool')
parser.add_argument('trace_file', help='Path to log trace file', type=str)
parser.add_argument('elf_file', help='Path to program ELF file', type=str)
# parser.add_argument('--print-details', '-d', help='Print detailed stats', action='store_true')
parser.add_argument('--no-errors', '-n', help='Do not print errors', action='store_true')
args = parser.parse_args()
# parse trace file
try:
print "Parse trace file '%s'..." % args.trace_file
lrecs = logtrace_parse(args.trace_file);
print "Parsing completed."
except ESPLogTraceParserError as e:
print "Failed to parse log trace (%s)!" % e
sys.exit(2)
# print recs
# get format strings and print info
print "===================================================================="
try:
logtrace_formated_print(lrecs, args.elf_file, args.no_errors);
except ESPLogTraceParserError as e:
print "Failed to print log trace (%s)!" % e
sys.exit(2)
print "\n====================================================================\n"
print "Log records count: %d" % len(lrecs)
if __name__ == '__main__':
main()

59
tools/esp_app_trace/pylibelf/.gitignore vendored Normal file
View File

@ -0,0 +1,59 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
*.swp
*.swo
*.swn

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 d1m0
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,5 @@
pylibelf
========
Python binding for libelf.

View File

@ -0,0 +1,155 @@
from types import *
from constants import *
from ctypes import *
lelf=CDLL("libelf.so.1")
__all__ = []
all_objs = []
class ElfError(Exception):
def __init__(self, msg):
self.msg = msg
self.errno = elf_errno()
self.elfmsg = elf_errmsg(self.errno)
def __str__(self):
return "ElfError(%d, %s): %s" % (self.errno, self.elfmsg, self.msg)
__all__.append("ElfError")
def nonNullDec(f):
def decorated(*args):
res = f(*args)
try:
a = res.contents
all_objs.append(res)
except ValueError: # NULL
raise ElfError(f.__name__ + " returned NULL")
return res
return decorated
def nonNegDec(f):
def decorated(*args):
res = f(*args)
if 0 > res:
raise ElfError(f.__name__ + " returned %d" % (res,))
return res
return decorated
def badValDec(badVal):
def decorator(f):
def decorated(*args):
res = f(*args)
if res == badVal:
raise ElfError(f.__name__ + " returned %s" % (str(res),))
return res
return decorated
return decorator
def define(f, argtypes, restype, err_decorator = None):
f.argtypes = argtypes
f.restype = restype
name = f.__name__
__all__.append(name)
if (err_decorator != None):
f = err_decorator(f)
globals()[name] = f
define(lelf.elf_version, [ c_int ], c_int )
if (elf_version(EV_CURRENT) == EV_NONE):
raise Exception("Version mismatch")
off_t = c_size_t # TODO(dbounov): Figure out actual off_t type
define(lelf.elf_begin, [ c_int, Elf_Cmd, ElfP ], ElfP)
define(lelf.elf_getident, [ ElfP, POINTER(c_int) ], POINTER(Elf_IdentT), nonNullDec)
define(lelf.elf_end, [ ElfP ], c_int, nonNegDec )
define(lelf.elf_cntl, [ ElfP, c_int ], c_int, nonNegDec)
define(lelf.elf_errmsg, [ c_int ], c_char_p)
define(lelf.elf_errno, [ ], c_int)
define(lelf.elf_fill, [ c_int ], None)
define(lelf.elf_flagdata, [ Elf_DataP, c_int, c_uint ], c_uint)
define(lelf.elf_flagehdr, [ ElfP, c_int, c_uint ], c_uint)
define(lelf.elf_flagelf, [ ElfP, c_int, c_uint ], c_uint)
define(lelf.elf_flagphdr, [ ElfP, c_int, c_uint ], c_uint)
define(lelf.elf_flagscn, [ Elf_ScnP, c_int, c_uint ], c_uint)
define(lelf.elf_flagshdr, [ Elf_ScnP, c_int, c_uint ], c_uint)
define(lelf.elf_getarhdr, [ ElfP ], POINTER(Elf_Arhdr))
#define(lelf.elf_getarsym, [ ], )
define(lelf.elf_getbase, [ ElfP ], off_t, nonNegDec)
define(lelf.elf_getdata, [ Elf_ScnP, Elf_DataP ], Elf_DataP)
define(lelf.elf_getscn, [ ElfP, c_size_t ], Elf_ScnP, nonNullDec )
define(lelf.elf_getshnum, [ ElfP, POINTER(c_size_t) ], c_int, nonNegDec )
define(lelf.elf_getshstrndx, [ ElfP, POINTER(c_size_t) ], c_int, nonNegDec )
define(lelf.elf_hash, [ c_char_p ], c_ulong)
define(lelf.elf_kind, [ ElfP ], c_int )
define(lelf.elf_memory, [ POINTER(c_char), c_size_t ], ElfP, nonNullDec)
define(lelf.elf_ndxscn, [ Elf_ScnP ], c_size_t, badValDec(SHN_UNDEF))
define(lelf.elf_newdata, [ Elf_ScnP ], Elf_DataP, nonNullDec)
define(lelf.elf_newscn, [ ElfP ], Elf_ScnP, nonNullDec)
#define(lelf.elf_next, [ ], )
define(lelf.elf_nextscn, [ ElfP, Elf_ScnP ], Elf_ScnP)
#define(lelf.elf_rand, [ ], )
define(lelf.elf_rawdata, [ Elf_ScnP, Elf_DataP ], Elf_DataP)
#define(lelf.elf_rawfile, [ ], )
define(lelf.elf_strptr, [ ElfP, c_size_t, c_size_t ], c_char_p)
define(lelf.elf_update, [ ElfP, c_int], off_t, nonNegDec)
define(lelf.elf32_checksum, [ ElfP ], c_long)
define(lelf.elf32_fsize, [ c_int, c_size_t, c_uint ], c_size_t, nonNegDec)
define(lelf.elf32_getehdr, [ ElfP ], POINTER(Elf32_Ehdr), nonNullDec)
define(lelf.elf32_getphdr, [ ElfP ], POINTER(Elf32_Phdr), nonNullDec)
define(lelf.elf32_getshdr, [ Elf_ScnP ], POINTER(Elf32_Shdr), nonNullDec)
define(lelf.elf32_newehdr, [ ElfP ], POINTER(Elf32_Ehdr), nonNullDec)
define(lelf.elf32_newphdr, [ ElfP, c_size_t ], POINTER(Elf32_Phdr), nonNullDec)
define(lelf.elf32_xlatetof, [ Elf_DataP, Elf_DataP, c_uint ], Elf_DataP, nonNullDec)
define(lelf.elf32_xlatetom, [ Elf_DataP, Elf_DataP, c_uint ], Elf_DataP, nonNullDec)
define(lelf.elf64_checksum, [ ElfP ], c_long )
define(lelf.elf64_fsize, [ c_int, c_size_t, c_uint ], c_size_t, nonNegDec)
define(lelf.elf64_getehdr,[ ElfP ], POINTER(Elf64_Ehdr), nonNullDec)
define(lelf.elf64_getphdr, [ ElfP ], POINTER(Elf64_Phdr), nonNullDec)
define(lelf.elf64_getshdr, [ Elf_ScnP ], POINTER(Elf64_Shdr), nonNullDec)
define(lelf.elf64_newehdr, [ ElfP ], POINTER(Elf64_Ehdr), nonNullDec)
define(lelf.elf64_newphdr, [ ElfP, c_size_t ], POINTER(Elf64_Phdr), nonNullDec)
define(lelf.elf64_xlatetof, [ Elf_DataP, Elf_DataP, c_uint ], Elf_DataP, nonNullDec)
define(lelf.elf64_xlatetom, [ Elf_DataP, Elf_DataP, c_uint ], Elf_DataP, nonNullDec)
# NOTE(dbounov): Ignoring gelf functions for now
#define(lelf.gelf_checksum, [ ], )
#define(lelf.gelf_fsize, [ ], )
#define(lelf.gelf_getcap, [ ], )
#define(lelf.gelf_getclass, [ ], )
#define(lelf.gelf_getdyn, [ ], )
#define(lelf.gelf_getehdr, [ ], )
#define(lelf.gelf_getmove, [ ], )
#define(lelf.gelf_getphdr, [ ], )
#define(lelf.gelf_getrel, [ ], )
#define(lelf.gelf_getrela, [ ], )
#define(lelf.gelf_getshdr, [ ], )
#define(lelf.gelf_getsym, [ ], )
#define(lelf.gelf_getsyminfo, [ ], )
#define(lelf.gelf_getsymshndx, [ ], )
#define(lelf.gelf_newehdr, [ ], )
#define(lelf.gelf_newphdr, [ ], )
#define(lelf.gelf_update_cap, [ ], )
#define(lelf.gelf_update_dyn, [ ], )
#define(lelf.gelf_update_ehdr, [ ], )
#define(lelf.gelf_update_move, [ ], )
#define(lelf.gelf_update_phdr, [ ], )
#define(lelf.gelf_update_rel, [ ], )
#define(lelf.gelf_update_rela, [ ], )
#define(lelf.gelf_update_shdr, [ ], )
#define(lelf.gelf_update_sym, [ ], )
#define(lelf.gelf_update_symshndx, [ ], )
#define(lelf.gelf_update_syminfo, [ ], )
#define(lelf.gelf_xlatetof, [ ], )
#define(lelf.gelf_xlatetom, [ ], )
#define(lelf.nlist, [ ], )

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,216 @@
import sys
import os
from .. import *
from ..constants import *
from ..types import *
from ..util import *
from ctypes import *
def sections(elf, **kwargs):
i = None
ndx = 0 # we skip the first null section
if 'info' in kwargs:
if (isinstance(kwargs['info'], Elf_Scn)):
info = elf_ndxscn(kwargs['info'])
else:
info = kwargs['info']
else:
info = None
while 1:
i = elf_nextscn(elf, i)
ndx += 1
if (not bool(i)):
break
try:
if ('name' in kwargs and section_name(elf, i) != kwargs['name']):
continue
if ('type' in kwargs and section_type(elf, i) != kwargs['type']):
continue
if ('link' in kwargs and section_link(elf, i) != kwargs['link']):
continue
if (info != None and section_hdr(elf, i).sh_info != info):
continue
except ValueError:
print "Error iterating over section ", i
continue
if ('ndx' in kwargs and kwargs['ndx']):
yield (ndx, i.contents)
else:
yield i.contents
def shdrs(elf):
i = None
while 1:
i = elf_nextscn(elf, i)
if (not bool(i)):
break
yield select(elf, 'getshdr')(i.contents).contents
def phdrs(elf):
phdrTbl = select(elf, "getphdr")(elf)
ehdr = select(elf, "getehdr")(elf).contents
phdrCnt = ehdr.e_phnum
for i in xrange(0, phdrCnt):
yield phdrTbl[i]
def data(elf_scn):
i = None
while 1:
i = elf_getdata(elf_scn, i)
if (not bool(i)):
break
yield i.contents
def strings(v):
if (isinstance(v, Elf_Data)):
strtab_data = v
size = strtab_data.d_size
buf = cast(strtab_data.d_buf, POINTER(c_char))
start = 0;
while start < size:
end = start;
while buf[end] != '\x00': end += 1
yield (strtab_data.d_off + start, buf[start:end])
start = end+1
elif (isinstance(v, Elf_Scn)):
for d in data(v):
strtab_data = d
size = strtab_data.d_size
buf = cast(strtab_data.d_buf, POINTER(c_char))
start = 0;
while start < size:
end = start;
while buf[end] != '\x00': end += 1
yield (strtab_data.d_off + start, buf[start:end])
start = end+1
def arr_iter(data, itemT, ind = False):
size = data.d_size
if size % sizeof(itemT) != 0:
raise Exception("Data size not a multiple of symbol size!")
buf = cast(data.d_buf, POINTER(itemT))
nelems = size / sizeof(itemT)
for i in xrange(0, nelems):
if ind:
yield (i, buf[i])
else:
yield buf[i]
def syms(elf, v = None):
symT = Elf32_Sym if (is32(elf)) else Elf64_Sym
if v == None:
for s in sections(elf):
hdr = section_hdr(elf, s)
if (hdr.sh_type != SHT_SYMTAB and hdr.sh_type != SHT_DYNSYM):
continue
for d in data(s):
for (ind, sym) in arr_iter(d, symT, True):
yield (ind, sym)
elif isinstance(v, Elf_Scn):
for d in data(v):
for (ind, sym) in arr_iter(d, symT, True):
yield (ind, sym)
else:
assert isinstance(v, Elf_Data)
for (ind, sym) in arr_iter(v, symT, True):
yield (ind, sym)
def rels(elf, **kwargs):
relT = Elf32_Rel if (is32(elf)) else Elf64_Rel
if 'section' in kwargs:
secl = sections(elf, type = SHT_REL, info = kwargs['section'])
else:
secl = sections(elf, type = SHT_REL)
if 'range' in kwargs:
for scn in secl:
for d in data(scn):
for rel in arr_iter(d, relT):
if (rel.r_offset >= kwargs['range'][0] and
rel.r_offset < kwargs['range'][1]):
yield (rel, section_hdr(elf, scn).sh_link)
else:
for scn in secl:
for d in data(scn):
for rel in arr_iter(d, relT):
yield (rel, section_hdr(elf, scn).sh_link)
def relas(elf, **kwargs):
relT = Elf32_Rela if (is32(elf)) else Elf64_Rela
if 'section' in kwargs:
scn = kwargs['section']
if (type(scn) == str): scn = list(sections(elf, name=scn))[0]
if (isinstance(scn, Elf_Scn)): scn = elf_ndxscn(byref(scn))
secl = list(sections(elf, type = SHT_RELA, info = scn))
else:
secl = list(sections(elf, type = SHT_RELA))
if 'range' in kwargs:
for scn in secl:
for d in data(scn):
for rel in arr_iter(d, relT):
if (rel.r_offset + rel.r_addend >= kwargs['range'][0] and
rel.r_offset + rel.r_addend < kwargs['range'][1]):
yield (rel, section_hdr(elf, scn).sh_link)
else:
addSecId = kwargs['withSectionId']==True \
if 'withSectionId' in kwargs \
else False
if not addSecId:
for scn in secl:
for d in data(scn):
for rel in arr_iter(d, relT):
yield (rel, section_hdr(elf, scn).sh_link)
else:
for scn in secl:
for d in data(scn):
for rel in arr_iter(d, relT):
yield (rel, section_hdr(elf, scn).sh_info)
def getOnlyData(scn):
d = elf_getdata(scn, None);
assert bool(elf_getdata(scn, d)) == False
return d
def dyns(elf):
relT = Elf64_Dyn
for scn in sections(elf, name=".dynamic"):
for d in data(scn):
for dyn in arr_iter(d, relT):
yield dyn
def elfs(fname):
fd = os.open(fname, os.O_RDONLY)
ar = elf_begin(fd, ELF_C_READ, None)
i = None
while 1:
i = elf_begin(fd, ELF_C_READ, ar)
if (not bool(i)):
break
yield i
elf_end(ar)
os.close(fd)

View File

@ -0,0 +1,55 @@
def ELF32_R_SYM(i):
if type(i) == str:
assert(len(i) == 1) # Single char
i = ord(i)
return i >> 8
def ELF32_R_TYPE(i):
if type(i) == str:
assert(len(i) == 1) # Single char
i = ord(i)
return i % 256 # Lowest 8 bits
def ELF32_R_INFO(sym, typ):
return (((sym) << 8) + typ % 256)
def ELF64_R_SYM(i):
if type(i) == str:
assert(len(i) == 1) # Single char
i = ord(i)
return i >> 32
def ELF64_R_TYPE(i):
if type(i) == str:
assert(len(i) == 1) # Single char
i = ord(i)
return i & 0xffffffffL
def ELF64_R_INFO(sym, typ):
return ((sym << 32) + (typ & 0xffffffffL))
# symbol st_info
def ELF32_ST_BIND(val):
if type(val) == str:
assert(len(val) == 1) # Single char
val = ord(val)
return val >> 4
def ELF32_ST_TYPE(val):
if type(val) == str:
assert(len(val) == 1) # Single char
val = ord(val)
return val & 0xf
def ELF32_ST_INFO(bind, type):
return (((bind) << 4) + ((type) & 0xf))
def ELF64_ST_BIND(val):
return ELF32_ST_BIND(val)
def ELF64_ST_TYPE(val):
return ELF32_ST_TYPE(val)
def ELF64_ST_INFO(bind, type):
return ELF32_ST_INFO(bind, type)

View File

@ -0,0 +1,274 @@
from ctypes import *
from ..constants import EI_NIDENT
# Obtained from /usr/lib/elf.h
# Type for a 16-bit quantity.
Elf32_Half = c_uint16
Elf64_Half = c_uint16
# Types for signed and unsigned 32-bit quantities.
Elf32_Word = c_uint32
Elf32_Sword = c_int32
Elf64_Word = c_uint32
Elf64_Sword = c_int32
# Types for signed and unsigned 64-bit quantities.
Elf32_Xword = c_uint64
Elf32_Sxword = c_int64
Elf64_Xword = c_uint64
Elf64_Sxword = c_int64
# Type of addresses.
Elf32_Addr = c_uint32
Elf64_Addr = c_uint64
# Type of file offsets.
Elf32_Off = c_uint32
Elf64_Off = c_uint64
# Type for section indices, which are 16-bit quantities.
Elf32_Section = c_uint16
Elf64_Section = c_uint16
# Type for version symbol information.
Elf32_Versym = Elf32_Half
Elf64_Versym = Elf64_Half
# The ELF file header. This appears at the start of every ELF file.
Elf_IdentT = c_char * EI_NIDENT
Elf_Cmd = c_int
class _ElfStructure(Structure):
def __str__(self):
return self.__class__.__name__ + '(' + \
','.join([field[0] + '=' + str(getattr(self, field[0])) for field in self._fields_]) + ')'
class _ElfUnion(Union):
def __str__(self):
return self.__class__.__name__ + '(' + \
','.join([field[0] + '=' + str(getattr(self, field[0])) for field in self._fields_]) + ')'
# Libelf opaque handles
class Elf(_ElfStructure):
_fields_ = []
class Elf_Scn(_ElfStructure):
_fields_ = []
class Elf_Data(_ElfStructure):
_fields_ = [
('d_buf', c_void_p),
('d_type', c_int),
('d_size', c_size_t),
('d_off', c_size_t),
('d_align', c_size_t),
('d_version', c_uint)
]
ElfP = POINTER(Elf)
Elf_ScnP = POINTER(Elf_Scn)
Elf_DataP = POINTER(Elf_Data)
class Elf32_Ehdr(_ElfStructure):
_fields_ = [
('e_ident', Elf_IdentT ), # Magic number and other info
('e_type', Elf32_Half ), # Object file type
('e_machine', Elf32_Half ), # Architecture
('e_version', Elf32_Word ), # Object file version
('e_entry', Elf32_Addr ), # Entry point virtual address
('e_phoff', Elf32_Off), # Program header table file offset
('e_shoff', Elf32_Off), # Section header table file offset
('e_flags', Elf32_Word ), # Processor-specific flags
('e_ehsize', Elf32_Half ), # ELF header size in bytes
('e_phentsize', Elf32_Half ), # Program header table entry size
('e_phnum', Elf32_Half ), # Program header table entry count
('e_shentsize', Elf32_Half ), # Section header table entry size
('e_shnum', Elf32_Half ), # Section header table entry count
('e_shstrndx', Elf32_Half ), # Section header string table index
]
class Elf64_Ehdr(_ElfStructure):
_fields_ = [
('e_ident', Elf_IdentT ), # Magic number and other info
('e_type', Elf64_Half ), # Object file type
('e_machine', Elf64_Half ), # Architecture
('e_version', Elf64_Word ), # Object file version
('e_entry', Elf64_Addr ), # Entry point virtual address
('e_phoff', Elf64_Off), # Program header table file offset
('e_shoff', Elf64_Off), # Section header table file offset
('e_flags', Elf64_Word ), # Processor-specific flags
('e_ehsize', Elf64_Half ), # ELF header size in bytes
('e_phentsize', Elf64_Half ), # Program header table entry size
('e_phnum', Elf64_Half ), # Program header table entry count
('e_shentsize', Elf64_Half ), # Section header table entry size
('e_shnum', Elf64_Half ), # Section header table entry count
('e_shstrndx', Elf64_Half ), # Section header string table index
]
class Elf32_Shdr(_ElfStructure):
_fields_ = [
('sh_name', Elf32_Word), # Section name (string tbl index)
('sh_type', Elf32_Word), # Section type
('sh_flags', Elf32_Word), # Section flags
('sh_addr', Elf32_Addr), # Section virtual addr at execution
('sh_offset', Elf32_Off), # Section file offset
('sh_size', Elf32_Word), # Section size in bytes
('sh_link', Elf32_Word), # Link to another section
('sh_info', Elf32_Word), # Additional section information
('sh_addralign', Elf32_Word), # Section alignment
('sh_entsize', Elf32_Word), # Entry size if section holds table
]
class Elf64_Shdr(_ElfStructure):
_fields_ = [
('sh_name', Elf64_Word), # Section name (string tbl index)
('sh_type', Elf64_Word), # Section type
('sh_flags', Elf64_Xword), # Section flags
('sh_addr', Elf64_Addr), # Section virtual addr at execution
('sh_offset', Elf64_Off), # Section file offset
('sh_size', Elf64_Xword), # Section size in bytes
('sh_link', Elf64_Word), # Link to another section
('sh_info', Elf64_Word), # Additional section information
('sh_addralign', Elf64_Xword), # Section alignment
('sh_entsize', Elf64_Xword), # Entry size if section holds table
]
class Elf32_Phdr(_ElfStructure):
_fields_ = [
('p_type', Elf32_Word), # Segment type
('p_offset', Elf32_Off), # Segment file offset
('p_vaddr', Elf32_Addr), # Segment virtual address
('p_paddr', Elf32_Addr), # Segment physical address
('p_filesz', Elf32_Word), # Segment size in file
('p_memsz', Elf32_Word), # Segment size in memory
('p_flags', Elf32_Word), # Segment flags
('p_align', Elf32_Word), # Segment alignment
]
class Elf64_Phdr(_ElfStructure):
_fields_ = [
('p_type', Elf64_Word), # Segment type
('p_flags', Elf64_Word), # Segment flags
('p_offset', Elf64_Off), # Segment file offset
('p_vaddr', Elf64_Addr), # Segment virtual address
('p_paddr', Elf64_Addr), # Segment physical address
('p_filesz', Elf64_Xword), # Segment size in file
('p_memsz', Elf64_Xword), # Segment size in memory
('p_align', Elf64_Xword), # Segment alignment
]
# /* Symbol table entry. */
class Elf32_Sym(_ElfStructure):
_fields_ = [
('st_name', Elf32_Word), # Symbol name (string tbl index)
('st_value', Elf32_Addr), # Symbol value
('st_size', Elf32_Word), # Symbol size
('st_info', c_char), # Symbol type and binding
('st_other', c_char), # Symbol visibility
('st_shndx', Elf32_Section), # Section index
]
class Elf64_Sym(_ElfStructure):
_fields_ = [
('st_name', Elf64_Word), # Symbol name (string tbl index)
('st_info', c_char), # Symbol type and binding
('st_other', c_char), # Symbol visibility
('st_shndx', Elf64_Section), # Section index
('st_value', Elf64_Addr), # Symbol value
('st_size', Elf64_Xword), # Symbol size
]
#/* The syminfo section if available contains additional information about
# every dynamic symbol. */
class Elf32_Syminfo(_ElfStructure):
_fields_ = [
('si_boundto', Elf32_Half), # Direct bindings, symbol bound to
('si_flags', Elf32_Half), # Per symbol flags
]
class Elf64_Syminfo(_ElfStructure):
_fields_ = [
('si_boundto', Elf64_Half), # Direct bindings, symbol bound to
('si_flags', Elf64_Half), # Per symbol flags
]
# /* Relocation table entry without addend (in section of type SHT_REL). */
class Elf32_Rel(_ElfStructure):
_fields_ = [
('r_offset', Elf32_Addr), # Address
('r_info', Elf32_Word), # Relocation type and symbol index
]
class Elf64_Rel(_ElfStructure):
_fields_ = [
('r_offset', Elf64_Addr), # Address
('r_info', Elf64_Xword), # Relocation type and symbol index
]
# # Relocation table entry with addend (in section of type SHT_RELA).
class Elf32_Rela(_ElfStructure):
_fields_ = [
('r_offset', Elf32_Addr), # Address
('r_info', Elf32_Word), # Relocation type and symbol index
('r_addend', Elf32_Sword), # Addend
]
class Elf64_Rela(_ElfStructure):
_fields_ = [
('r_offset', Elf64_Addr), # Address
('r_info', Elf64_Xword), # Relocation type and symbol index
('r_addend', Elf64_Sxword), # Addend
]
time_t = c_int64
uid_t = c_int32
gid_t = c_int32
mode_t = c_int32
off_t = c_int64
class Elf_Arhdr(_ElfStructure):
_fields_ = [
('ar_name', c_char_p),
('ar_date', time_t),
('ar_uid', uid_t),
('ar_gid', gid_t),
('ar_mode', mode_t),
('ar_size', off_t),
('ar_fmag', POINTER(c_char)),
]
class _Elf64_DynUnion(_ElfUnion):
_fields_ = [
('d_val', Elf64_Xword),
('d_ptr', Elf64_Addr),
]
class Elf64_Dyn(_ElfStructure):
_fields_ = [
('d_tag', Elf64_Xword),
('d_un', _Elf64_DynUnion),
]
# GNU Extensions
class Elf64_Verneed(_ElfStructure):
_fields_ = [
('vn_version', Elf64_Half),
('vn_cnt', Elf64_Half),
('vn_file', Elf64_Word),
('vn_aux', Elf64_Word),
('vn_next', Elf64_Word),
]
class Elf64_Vernaux(_ElfStructure):
_fields_ = [
('vna_hash', Elf64_Word),
('vna_flags', Elf64_Half),
('vna_other', Elf64_Half),
('vna_name', Elf64_Word),
('vna_next', Elf64_Word),
]

View File

@ -0,0 +1,38 @@
from .. import *
from ..types import *
from ..constants import *
from ctypes import *
import os
def _class(elf): return ord(elf_getident(elf, None).contents[EI_CLASS])
def is32(elf): return _class(elf) == ELFCLASS32
def is64(elf): return _class(elf) == ELFCLASS64
def select(elf, fname):
if is32(elf):
return globals()['elf32_' + fname]
else:
return globals()['elf64_' + fname]
def section_name(elfP, secP):
shstrndx = c_size_t()
r = elf_getshstrndx(elfP, byref(shstrndx))
shdr = select(elfP, 'getshdr')(secP)
return elf_strptr(elfP, shstrndx, shdr.contents.sh_name)
def section_type(elfP, secP):
return select(elfP, 'getshdr')(secP).contents.sh_type
def section_link(elfP, secP):
return select(elfP, 'getshdr')(secP).contents.sh_link
def section_hdr(elfP, secP):
return select(elfP, 'getshdr')(secP).contents
def open_elf(fname):
fd = os.open(fname, os.O_RDONLY)
return elf_begin(fd, ELF_C_READ, None)
def sym_name(elf, scn, sym):
return elf_strptr(elf, section_link(elf, scn), sym.st_name)

View File

@ -0,0 +1,58 @@
from .. import *
from ...types import *
from ...iterators import *
def defined(s): return s.st_shndx != SHN_UNDEF
def defines(elf, symN):
s = findSymbol(elf, symN)
print elf, symN, s
if s != None:
print s.st_shndx, s.st_name
return s != None and defined(s[1])
def derefSymbol(elf, s):
assert defined(s)
if s.st_shndx == SHN_ABS:
raise Exception("NYI")
else:
scn = elf_getscn(elf, s.st_shndx)
shdr = section_hdr(elf, scn);
off = 0
base = shdr.sh_addr if shdr.sh_addr != 0 else 0
start = s.st_value
end = s.st_value + s.st_size
r = ''
for d in data(scn):
if start >= end: break;
off = base + d.d_off
if start >= off and start < off + d.d_size:
c = cast(d.d_buf, POINTER(c_char))
l = min(off + d.d_size, end) - start
r += c[start- off : start - off + l]
start += l
return r
def derefSymbolFull(elf, s):
""" Given an elf file and a Elf{32/64}_Sym defined in the elf file,
return a tuple with the contents of memory refered to by the symbol,
and any Rel's and Rela's inside that memory.
"""
assert (defined(s))
contents = derefSymbol(elf, s)
relL = list(rels(elf, section=s.st_shndx, \
range=(s.st_value, s.st_size + s.st_value)))
relaL = list(relas(elf, section=s.st_shndx, \
range=(s.st_value, s.st_size + s.st_value)))
return (contents, relL, relaL)
# Given a symbol name return the symbol and section in which it occurs
def findSymbol(elf, s):
for scn in sections(elf, type=SHT_SYMTAB):
strndx = section_link(elf, scn)
for d in data(scn):
for (ind, sym) in syms(elf, d):
if s == elf_strptr(elf, strndx, sym.st_name):
return (scn, sym)
return None

View File

@ -106,11 +106,17 @@ CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_MEMMAP_SMP=y
# CONFIG_MEMMAP_TRACEMEM is not set
# CONFIG_MEMMAP_TRACEMEM_TWOBANKS is not set
# CONFIG_ESP32_TRAX is not set
CONFIG_TRACEMEM_RESERVE_DRAM=0x0
# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set
# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
# CONFIG_ESP32_ENABLE_COREDUMP is not set
# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set
# CONFIG_ESP32_APPTRACE_DEST_UART is not set
CONFIG_ESP32_APPTRACE_DEST_NONE=y
# CONFIG_ESP32_APPTRACE_ENABLE is not set
# CONFIG_TWO_MAC_ADDRESS_FROM_EFUSE is not set
CONFIG_FOUR_MAC_ADDRESS_FROM_EFUSE=y
CONFIG_NUMBER_OF_MAC_ADDRESS_GENERATED_FROM_EFUSE=4