mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
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:
parent
ad50a70440
commit
55f1a63faf
@ -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"
|
||||
|
994
components/esp32/app_trace.c
Normal file
994
components/esp32/app_trace.c
Normal 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
|
@ -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) {
|
||||
|
123
components/esp32/include/esp_app_trace.h
Normal file
123
components/esp32/include/esp_app_trace.h
Normal 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
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
124
tools/esp_app_trace/apptrace_proc.py
Executable file
124
tools/esp_app_trace/apptrace_proc.py
Executable 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()
|
163
tools/esp_app_trace/logtrace_proc.py
Executable file
163
tools/esp_app_trace/logtrace_proc.py
Executable 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
59
tools/esp_app_trace/pylibelf/.gitignore
vendored
Normal 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
|
||||
|
22
tools/esp_app_trace/pylibelf/LICENSE
Normal file
22
tools/esp_app_trace/pylibelf/LICENSE
Normal 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.
|
||||
|
5
tools/esp_app_trace/pylibelf/README.md
Normal file
5
tools/esp_app_trace/pylibelf/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
pylibelf
|
||||
========
|
||||
|
||||
Python binding for libelf.
|
||||
|
155
tools/esp_app_trace/pylibelf/__init__.py
Normal file
155
tools/esp_app_trace/pylibelf/__init__.py
Normal 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, [ ], )
|
1850
tools/esp_app_trace/pylibelf/constants/__init__.py
Normal file
1850
tools/esp_app_trace/pylibelf/constants/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
216
tools/esp_app_trace/pylibelf/iterators/__init__.py
Normal file
216
tools/esp_app_trace/pylibelf/iterators/__init__.py
Normal 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)
|
||||
|
55
tools/esp_app_trace/pylibelf/macros/__init__.py
Normal file
55
tools/esp_app_trace/pylibelf/macros/__init__.py
Normal 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)
|
274
tools/esp_app_trace/pylibelf/types/__init__.py
Normal file
274
tools/esp_app_trace/pylibelf/types/__init__.py
Normal 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),
|
||||
]
|
38
tools/esp_app_trace/pylibelf/util/__init__.py
Normal file
38
tools/esp_app_trace/pylibelf/util/__init__.py
Normal 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)
|
58
tools/esp_app_trace/pylibelf/util/syms/__init__.py
Normal file
58
tools/esp_app_trace/pylibelf/util/syms/__init__.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user