esp32: Adds GCOV debug stubs support

Adds the following functionality
 - Debug stubs infrastructure
 - Stub for retrieveing GCOV data without user source code modification
This commit is contained in:
Alexey Gerenkov 2018-02-15 20:09:03 +03:00
parent f885e8b8de
commit c1b6a37bb1
10 changed files with 437 additions and 73 deletions

View File

@ -190,4 +190,12 @@ config SYSVIEW_EVT_TIMER_EXIT_ENABLE
Enables "Timer Exit" event.
endmenu
config ESP32_GCOV_ENABLE
bool "GCOV to Host Enable"
depends on ESP32_DEBUG_STUBS_ENABLE && ESP32_APPTRACE_ENABLE && !SYSVIEW_ENABLE
default y
help
Enables support for GCOV data transfer to host.
endmenu

View File

@ -336,6 +336,8 @@ typedef struct {
uint8_t *(*get_down_buffer)(uint32_t *, esp_apptrace_tmo_t *);
esp_err_t (*put_down_buffer)(uint8_t *, esp_apptrace_tmo_t *);
bool (*host_is_connected)(void);
esp_err_t (*status_reg_set)(uint32_t val);
esp_err_t (*status_reg_get)(uint32_t *val);
} esp_apptrace_hw_t;
static uint32_t esp_apptrace_trax_down_buffer_write_nolock(uint8_t *data, uint32_t size);
@ -345,6 +347,8 @@ static esp_err_t esp_apptrace_trax_put_buffer(uint8_t *ptr, esp_apptrace_tmo_t *
static bool esp_apptrace_trax_host_is_connected(void);
static uint8_t *esp_apptrace_trax_down_buffer_get(uint32_t *size, esp_apptrace_tmo_t *tmo);
static esp_err_t esp_apptrace_trax_down_buffer_put(uint8_t *ptr, esp_apptrace_tmo_t *tmo);
static esp_err_t esp_apptrace_trax_status_reg_set(uint32_t val);
static esp_err_t esp_apptrace_trax_status_reg_get(uint32_t *val);
static esp_apptrace_hw_t s_trace_hw[ESP_APPTRACE_HW_MAX] = {
{
@ -353,7 +357,9 @@ static esp_apptrace_hw_t s_trace_hw[ESP_APPTRACE_HW_MAX] = {
.flush_up_buffer = esp_apptrace_trax_flush,
.get_down_buffer = esp_apptrace_trax_down_buffer_get,
.put_down_buffer = esp_apptrace_trax_down_buffer_put,
.host_is_connected = esp_apptrace_trax_host_is_connected
.host_is_connected = esp_apptrace_trax_host_is_connected,
.status_reg_set = esp_apptrace_trax_status_reg_set,
.status_reg_get = esp_apptrace_trax_status_reg_get
}
};
@ -416,6 +422,8 @@ static void esp_apptrace_trax_init()
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));
// this is for OpenOCD to let him know where stub entries vector is resided
// must be read by host before any transfer using TRAX
eri_write(ESP_APPTRACE_TRAX_STAT_REG, 0);
ESP_APPTRACE_LOGI("Initialized TRAX on CPU%d", xPortGetCoreID());
@ -828,6 +836,18 @@ static bool esp_apptrace_trax_host_is_connected(void)
return eri_read(ESP_APPTRACE_TRAX_CTRL_REG) & ESP_APPTRACE_TRAX_HOST_CONNECT ? true : false;
}
static esp_err_t esp_apptrace_trax_status_reg_set(uint32_t val)
{
eri_write(ESP_APPTRACE_TRAX_STAT_REG, val);
return ESP_OK;
}
static esp_err_t esp_apptrace_trax_status_reg_get(uint32_t *val)
{
*val = eri_read(ESP_APPTRACE_TRAX_STAT_REG);
return ESP_OK;
}
static esp_err_t esp_apptrace_trax_dest_init()
{
for (int i = 0; i < ESP_APPTRACE_TRAX_BLOCKS_NUM; i++) {
@ -1159,6 +1179,24 @@ bool esp_apptrace_host_is_connected(esp_apptrace_dest_t dest)
{
esp_apptrace_hw_t *hw = NULL;
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
hw = ESP_APPTRACE_HW(ESP_APPTRACE_HW_TRAX);
#else
ESP_APPTRACE_LOGE("Application tracing via TRAX is disabled in menuconfig!");
return false;
#endif
} else {
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return false;
}
return hw->host_is_connected();
}
esp_err_t esp_apptrace_status_reg_set(esp_apptrace_dest_t dest, uint32_t val)
{
esp_apptrace_hw_t *hw = NULL;
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
hw = ESP_APPTRACE_HW(ESP_APPTRACE_HW_TRAX);
@ -1170,7 +1208,25 @@ bool esp_apptrace_host_is_connected(esp_apptrace_dest_t dest)
ESP_APPTRACE_LOGE("Trace destinations other then TRAX are not supported yet!");
return ESP_ERR_NOT_SUPPORTED;
}
return hw->host_is_connected();
return hw->status_reg_set(val);
}
esp_err_t esp_apptrace_status_reg_get(esp_apptrace_dest_t dest, uint32_t *val)
{
esp_apptrace_hw_t *hw = NULL;
if (dest == ESP_APPTRACE_DEST_TRAX) {
#if CONFIG_ESP32_APPTRACE_DEST_TRAX
hw = ESP_APPTRACE_HW(ESP_APPTRACE_HW_TRAX);
#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 hw->status_reg_get(val);
}
#endif

View File

@ -21,20 +21,92 @@
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include "esp_app_trace.h"
#include "esp_dbg_stubs.h"
#if CONFIG_ESP32_APPTRACE_ENABLE
#if CONFIG_ESP32_GCOV_ENABLE
#define ESP_GCOV_DOWN_BUF_SIZE 4200
#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include "esp_log.h"
const static char *TAG = "esp_gcov_rtio";
static void (*s_gcov_exit)(void);
static uint8_t s_gcov_down_buf[256];
/* TODO: remove code extracted from GCC when IDF toolchain will be updated */
/*=============== GCC CODE START ====================*/
/* Root of a program/shared-object state */
struct gcov_root
{
void *list;
unsigned dumped : 1; /* counts have been dumped. */
unsigned run_counted : 1; /* run has been accounted for. */
struct gcov_root *next;
struct gcov_root *prev;
};
/* Per-dynamic-object gcov state. */
extern struct gcov_root __gcov_root;
static void esp_gcov_reset_status(void)
{
__gcov_root.dumped = 0;
__gcov_root.run_counted = 0;
}
/*=============== GCC CODE END ====================*/
static int esp_dbg_stub_gcov_dump_do(void)
{
int ret = ESP_OK;
ESP_EARLY_LOGV(TAG, "Check for dump handler %p", s_gcov_exit);
if (s_gcov_exit) {
ESP_EARLY_LOGV(TAG, "Alloc apptrace down buf %d bytes", ESP_GCOV_DOWN_BUF_SIZE);
void *down_buf = malloc(ESP_GCOV_DOWN_BUF_SIZE);
if (down_buf == NULL) {
ESP_EARLY_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!", ret);
return ESP_ERR_NO_MEM;
}
ESP_EARLY_LOGV(TAG, "Config apptrace down buf");
esp_apptrace_down_buffer_config(down_buf, ESP_GCOV_DOWN_BUF_SIZE);
ESP_EARLY_LOGV(TAG, "Dump data... %p", s_gcov_exit);
s_gcov_exit();
ESP_EARLY_LOGV(TAG, "Free apptrace down buf");
free(down_buf);
}
ESP_EARLY_LOGV(TAG, "Finish file transfer session");
ret = esp_apptrace_fstop(ESP_APPTRACE_DEST_TRAX);
if (ret != ESP_OK) {
ESP_EARLY_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!", ret);
}
return ret;
}
/**
* @brief Triggers gcov info dump.
* This function is to be called by OpenOCD, not by normal user code.
* TODO: what about interrupted flash access (when cache disabled)???
*
* @return ESP_OK on success, otherwise see esp_err_t
*/
static int esp_dbg_stub_gcov_entry(void)
{
int ret = ESP_OK;
// disable IRQs on this CPU, other CPU is halted by OpenOCD
unsigned irq_state = portENTER_CRITICAL_NESTED();
ret = esp_dbg_stub_gcov_dump_do();
// reset dump status to allow incremental data accumulation
esp_gcov_reset_status();
portEXIT_CRITICAL_NESTED(irq_state);
return ret;
}
void esp_gcov_dump()
{
#if CONFIG_FREERTOS_UNICORE == 0
esp_cpu_stall(!xPortGetCoreID());
int other_core = xPortGetCoreID() ? 0 : 1;
esp_cpu_stall(other_core);
#endif
while (!esp_apptrace_host_is_connected(ESP_APPTRACE_DEST_TRAX)) {
@ -48,46 +120,59 @@ void esp_gcov_dump()
TIMERG1.wdt_wprotect=0;
}
if (s_gcov_exit) {
esp_apptrace_down_buffer_config(s_gcov_down_buf, sizeof(s_gcov_down_buf));
s_gcov_exit();
}
int ret = esp_apptrace_fstop(ESP_APPTRACE_DEST_TRAX);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!\n", ret);
}
esp_dbg_stub_gcov_dump_do();
// reset dump status to allow incremental data accumulation
esp_gcov_reset_status();
#if CONFIG_FREERTOS_UNICORE == 0
esp_cpu_unstall(other_core);
#endif
}
int gcov_rtio_atexit(void (*function)(void))
{
ESP_EARLY_LOGV(TAG, "%s %p", __FUNCTION__, function);
s_gcov_exit = function;
esp_dbg_stub_entry_set(ESP_DBG_STUB_ENTRY_GCOV, (uint32_t)&esp_dbg_stub_gcov_entry);
return 0;
}
void *gcov_rtio_fopen(const char *path, const char *mode)
{
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
return esp_apptrace_fopen(ESP_APPTRACE_DEST_TRAX, path, mode);
}
int gcov_rtio_fclose(void *stream)
{
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
return esp_apptrace_fclose(ESP_APPTRACE_DEST_TRAX, stream);
}
size_t gcov_rtio_fread(void *ptr, size_t size, size_t nmemb, void *stream)
{
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
return esp_apptrace_fread(ESP_APPTRACE_DEST_TRAX, ptr, size, nmemb, stream);
}
size_t gcov_rtio_fwrite(const void *ptr, size_t size, size_t nmemb, void *stream)
{
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
return esp_apptrace_fwrite(ESP_APPTRACE_DEST_TRAX, ptr, size, nmemb, stream);
}
int gcov_rtio_fseek(void *stream, long offset, int whence)
{
return esp_apptrace_fseek(ESP_APPTRACE_DEST_TRAX, stream, offset, whence);
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
int ret = esp_apptrace_fseek(ESP_APPTRACE_DEST_TRAX, stream, offset, whence);
ESP_EARLY_LOGV(TAG, "%s EXIT", __FUNCTION__);
return ret;
}
long gcov_rtio_ftell(void *stream)
{
return esp_apptrace_ftell(ESP_APPTRACE_DEST_TRAX, stream);
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
long ret = esp_apptrace_ftell(ESP_APPTRACE_DEST_TRAX, stream);
ESP_EARLY_LOGV(TAG, "%s", __FUNCTION__);
return ret;
}
#endif

View File

@ -87,6 +87,7 @@ static esp_err_t esp_apptrace_file_cmd_send(esp_apptrace_dest_t dest, uint8_t cm
esp_err_t ret;
esp_apptrace_fcmd_hdr_t *hdr;
ESP_EARLY_LOGV(TAG, "%s %d", __func__, cmd);
uint8_t *ptr = esp_apptrace_buffer_get(dest, sizeof(*hdr) + args_len, ESP_APPTRACE_TMO_INFINITE); //TODO: finite tmo
if (ptr == NULL) {
return ESP_ERR_NO_MEM;
@ -101,13 +102,13 @@ static esp_err_t esp_apptrace_file_cmd_send(esp_apptrace_dest_t dest, uint8_t cm
// now indicate that this buffer is ready to be sent off to host
ret = esp_apptrace_buffer_put(dest, ptr, ESP_APPTRACE_TMO_INFINITE);//TODO: finite tmo
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to put apptrace buffer (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to put apptrace buffer (%d)!", ret);
return ret;
}
ret = esp_apptrace_flush(dest, ESP_APPTRACE_TMO_INFINITE);//TODO: finite tmo
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to flush apptrace buffer (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to flush apptrace buffer (%d)!", ret);
return ret;
}
@ -119,11 +120,12 @@ static esp_err_t esp_apptrace_file_rsp_recv(esp_apptrace_dest_t dest, uint8_t *b
uint32_t tot_rd = 0;
while (tot_rd < buf_len) {
uint32_t rd_size = buf_len - tot_rd;
esp_err_t ret = esp_apptrace_read(dest, buf, &rd_size, ESP_APPTRACE_TMO_INFINITE); //TODO: finite tmo
esp_err_t ret = esp_apptrace_read(dest, buf + tot_rd, &rd_size, ESP_APPTRACE_TMO_INFINITE); //TODO: finite tmo
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read (%d)!", ret);
return ret;
}
ESP_EARLY_LOGV(TAG, "%s read %d bytes", __FUNCTION__, rd_size);
tot_rd += rd_size;
}
@ -142,6 +144,8 @@ void *esp_apptrace_fopen(esp_apptrace_dest_t dest, const char *path, const char
{
esp_apptrace_fopen_args_t cmd_args;
ESP_EARLY_LOGV(TAG, "esp_apptrace_fopen '%s' '%s'", path, mode);
cmd_args.path = path;
cmd_args.path_len = strlen(path) + 1;
cmd_args.mode = mode;
@ -150,7 +154,7 @@ void *esp_apptrace_fopen(esp_apptrace_dest_t dest, const char *path, const char
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FOPEN, esp_apptrace_fopen_args_prepare,
&cmd_args, cmd_args.path_len+cmd_args.mode_len);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return NULL;
}
@ -158,7 +162,7 @@ void *esp_apptrace_fopen(esp_apptrace_dest_t dest, const char *path, const char
void *resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return NULL;
}
@ -180,7 +184,7 @@ int esp_apptrace_fclose(esp_apptrace_dest_t dest, void *stream)
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FCLOSE, esp_apptrace_fclose_args_prepare,
&cmd_args, sizeof(cmd_args));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return EOF;
}
@ -188,7 +192,7 @@ int esp_apptrace_fclose(esp_apptrace_dest_t dest, void *stream)
int resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return EOF;
}
@ -207,13 +211,15 @@ size_t esp_apptrace_fwrite(esp_apptrace_dest_t dest, const void *ptr, size_t siz
{
esp_apptrace_fwrite_args_t cmd_args;
ESP_EARLY_LOGV(TAG, "esp_apptrace_fwrite f %p l %d", stream, size*nmemb);
cmd_args.buf = (void *)ptr;
cmd_args.size = size * nmemb;
cmd_args.file = stream;
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FWRITE, esp_apptrace_fwrite_args_prepare,
&cmd_args, sizeof(cmd_args.file)+cmd_args.size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return 0;
}
@ -221,7 +227,7 @@ size_t esp_apptrace_fwrite(esp_apptrace_dest_t dest, const void *ptr, size_t siz
size_t resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return 0;
}
@ -240,12 +246,14 @@ size_t esp_apptrace_fread(esp_apptrace_dest_t dest, void *ptr, size_t size, size
{
esp_apptrace_fread_args_t cmd_args;
ESP_EARLY_LOGV(TAG, "esp_apptrace_fread f %p l %d", stream, size*nmemb);
cmd_args.size = size * nmemb;
cmd_args.file = stream;
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FREAD, esp_apptrace_fread_args_prepare,
&cmd_args, sizeof(cmd_args));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return 0;
}
@ -253,13 +261,13 @@ size_t esp_apptrace_fread(esp_apptrace_dest_t dest, void *ptr, size_t size, size
size_t resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return 0;
}
if (resp > 0) {
ret = esp_apptrace_file_rsp_recv(dest, ptr, resp);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read file data (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read file data (%d)!", ret);
return 0;
}
}
@ -277,13 +285,15 @@ int esp_apptrace_fseek(esp_apptrace_dest_t dest, void *stream, long offset, int
{
esp_apptrace_fseek_args_t cmd_args;
ESP_EARLY_LOGV(TAG, "esp_apptrace_fseek f %p o 0x%lx w %d", stream, offset, whence);
cmd_args.file = stream;
cmd_args.offset = offset;
cmd_args.whence = whence;
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FSEEK, esp_apptrace_fseek_args_prepare,
&cmd_args, sizeof(cmd_args));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return -1;
}
@ -291,7 +301,7 @@ int esp_apptrace_fseek(esp_apptrace_dest_t dest, void *stream, long offset, int
int resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return -1;
}
@ -313,7 +323,7 @@ int esp_apptrace_ftell(esp_apptrace_dest_t dest, void *stream)
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_FTELL, esp_apptrace_ftell_args_prepare,
&cmd_args, sizeof(cmd_args));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send file cmd (%d)!", ret);
return -1;
}
@ -321,7 +331,7 @@ int esp_apptrace_ftell(esp_apptrace_dest_t dest, void *stream)
int resp;
ret = esp_apptrace_file_rsp_recv(dest, (uint8_t *)&resp, sizeof(resp));
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to read response (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to read response (%d)!", ret);
return -1;
}
@ -330,9 +340,10 @@ int esp_apptrace_ftell(esp_apptrace_dest_t dest, void *stream)
int esp_apptrace_fstop(esp_apptrace_dest_t dest)
{
ESP_EARLY_LOGV(TAG, "%s", __func__);
esp_err_t ret = esp_apptrace_file_cmd_send(dest, ESP_APPTRACE_FILE_CMD_STOP, NULL, NULL, 0);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!", ret);
ESP_EARLY_LOGE(TAG, "Failed to send files transfer stop cmd (%d)!", ret);
}
return ret;
}

View File

@ -511,6 +511,13 @@ config ESP32_DEBUG_OCDAWARE
The FreeRTOS panic and unhandled exception handers can detect a JTAG OCD debugger and
instead of panicking, have the debugger stop on the offending instruction.
config ESP32_DEBUG_STUBS_ENABLE
bool "OpenOCD debug stubs"
default OPTIMIZATION_LEVEL_DEBUG
depends on !ESP32_TRAX
help
Debug stubs are used by OpenOCD to execute pre-compiled onboard code which does some useful debugging,
e.g. GCOV data dump.
config INT_WDT
bool "Interrupt watchdog"

View File

@ -61,6 +61,7 @@
#include "esp_panic.h"
#include "esp_core_dump.h"
#include "esp_app_trace.h"
#include "esp_dbg_stubs.h"
#include "esp_efuse.h"
#include "esp_spiram.h"
#include "esp_clk_internal.h"
@ -332,6 +333,9 @@ void start_cpu0_default(void)
#endif
#if CONFIG_SYSVIEW_ENABLE
SEGGER_SYSVIEW_Conf();
#endif
#if CONFIG_ESP32_DEBUG_STUBS_ENABLE
esp_dbg_stubs_init();
#endif
err = esp_pthread_init();
assert(err == ESP_OK && "Failed to init pthread module!");

View File

@ -0,0 +1,95 @@
// 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.
// This module implements debug/trace stubs. The stub is a piece of special code which can invoked by OpenOCD
// Currently one stub is used for GCOV functionality
//
#include "eri.h"
#include "xtensa-debug-module.h"
#include "esp_dbg_stubs.h"
#include "esp_attr.h"
#if CONFIG_ESP32_DEBUG_STUBS_ENABLE
/*
Debug stubs is actually a table of 4-byte entries. Every entry is equal to zero or must contain meaningfull data.
The first entry is a service one and has the followinf format:
- tramp_addr, 4 bytes; Address of buffer for trampoline/code. Max size is ESP_DBG_STUBS_CODE_BUF_SIZE.
- min_stack_addr, 4 bytes; Start of the buffer for minimal onboard stack or data. Max size is ESP_DBG_STUBS_STACK_MIN_SIZE.
- data_alloc, 4 bytes; Address of function to allocate memory on target.
- data_free, 4 bytes; Address of function to free target memory.
This entry is used by OpenOCD code to invoke other stub entries and allocate memory for them.
*/
#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include "esp_log.h"
const static char *TAG = "esp_dbg_stubs";
#define ESP_DBG_STUBS_TRAX_REG ERI_TRAX_TRIGGERPC
#define ESP_DBG_STUBS_CODE_BUF_SIZE 32
#define ESP_DBG_STUBS_STACK_MIN_SIZE 2048
#define DBG_STUB_TRAMP_ATTR IRAM_ATTR
static struct {
uint32_t tramp_addr;
uint32_t min_stack_addr; // minimal stack addr
uint32_t data_alloc;
uint32_t data_free;
} s_dbg_stubs_ctl_data;
static uint32_t s_stub_entry[ESP_DBG_STUB_ENTRY_MAX];
static uint8_t s_stub_min_stack[ESP_DBG_STUBS_STACK_MIN_SIZE];
static DBG_STUB_TRAMP_ATTR uint8_t s_stub_code_buf[ESP_DBG_STUBS_CODE_BUF_SIZE];
// TODO: all called funcs should be in IRAM to work with disabled flash cache
static void * esp_dbg_stubs_data_alloc(uint32_t size)
{
ESP_LOGV(TAG, "%s %d", __func__, size);
void *p = malloc(size);
ESP_LOGV(TAG, "%s EXIT %p", __func__, p);
return p;
}
static void esp_dbg_stubs_data_free(void *addr)
{
ESP_LOGV(TAG, "%s %p", __func__, addr);
free(addr);
ESP_LOGV(TAG, "%s EXIT %p", __func__, addr);
}
void esp_dbg_stubs_init()
{
s_dbg_stubs_ctl_data.tramp_addr = (uint32_t)s_stub_code_buf;
s_dbg_stubs_ctl_data.min_stack_addr = (uint32_t)s_stub_min_stack;
s_dbg_stubs_ctl_data.data_alloc = (uint32_t)esp_dbg_stubs_data_alloc;
s_dbg_stubs_ctl_data.data_free = (uint32_t)esp_dbg_stubs_data_free;
s_stub_entry[ESP_DBG_STUB_CONTROL_DATA] = (uint32_t)&s_dbg_stubs_ctl_data;
eri_write(ESP_DBG_STUBS_TRAX_REG, (uint32_t)s_stub_entry);
ESP_LOGV(TAG, "%s stubs %x", __func__, eri_read(ESP_DBG_STUBS_TRAX_REG));
}
esp_err_t esp_dbg_stub_entry_set(esp_dbg_stub_id_t id, uint32_t entry)
{
if (id < ESP_DBG_STUB_ENTRY_FIRST || id >= ESP_DBG_STUB_ENTRY_MAX) {
ESP_LOGE(TAG, "Invalid stub id %d!", id);
return ESP_ERR_INVALID_ARG;
}
s_stub_entry[id] = entry;
return ESP_OK;
}
#endif

View File

@ -0,0 +1,50 @@
// 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_DBG_STUBS_H_
#define ESP_DBG_STUBS_H_
#include "esp_err.h"
/**
* Debug stubs entries IDs
*/
typedef enum {
ESP_DBG_STUB_CONTROL_DATA, ///< stubs descriptor entry
ESP_DBG_STUB_ENTRY_FIRST,
ESP_DBG_STUB_ENTRY_GCOV ///< GCOV entry
= ESP_DBG_STUB_ENTRY_FIRST,
ESP_DBG_STUB_ENTRY_MAX
} esp_dbg_stub_id_t;
/**
* @brief Initializes debug stubs.
*
* @note Must be called after esp_apptrace_init() if app tracing is enabled.
*/
void esp_dbg_stubs_init(void);
/**
* @brief Initializes application tracing module.
*
* @note Should be called before any esp_apptrace_xxx call.
*
* @param id Stub ID.
* @param entry Stub entry. Usually it is stub entry function address,
* but can be any value meaningfull for OpenOCD command/code.
*
* @return ESP_OK on success, otherwise see esp_err_t
*/
esp_err_t esp_dbg_stub_entry_set(esp_dbg_stub_id_t id, uint32_t entry);
#endif //ESP_DBG_STUBS_H_

View File

@ -4,53 +4,98 @@ See the README.md file in the upper level 'examples' directory for more informat
GCC has useful feature which allows to generate code coverage information. Generated data show how many times every program execution paths has been taken.
Basing on coverage data developers can detect untested pieces of code and also it gives valuable information about critical (frequently used) execution paths.
In general case when coverage option is enabled GCC generates additional code to accumulate necessary data and save them into files. File system is not always available
in ESP32 based projects or size of the file storage can be very limited to keep all the coverage data. To overcome those limitations IDF provides functionality
to transfer the data to the host and save them on host file system. The data transfer is done via JTAG.
In general case when coverage option is enabled GCC generates additional code to accumulate necessary data and save them into files. File system is not always available in ESP32 based projects or size of the file storage can be very limited to keep all the coverage data. To overcome those limitations IDF provides functionality to transfer the data to host and save them on its file system. Data transfer is done via JTAG.
This example shows how to generate coverage information for the program.
## How To Gather Coverage Info
Below are the steps which should be performed to obtain coverage info. Steps 1-3 are already done for this example project. They should be performed if user wants to fork new IDF-based project and needs to collect coverage info.
There are two ways to collect gcov data:
* Hard-coded call to `esp_gcov_dump`.
* Instant run-time dumping w/o changes in your code via IDF's gcov debug stub.
### Generic Steps
Below are generic steps which should be performed to obtain coverage info. The steps are already done for this example project.
1. Enable application tracing module in menuconfig. Choose `Trace memory` in `Component config -> Application Level Tracing -> Data Destination`.
2. Enable coverage info generation for necessary source files. To do this add the following line to the 'component.mk' files of your project:
2. Enable GCOV to host interface in menuconfig `Component config -> Application Level Tracing -> GCOV to Host Enable`.
3. Enable coverage info generation for necessary source files. To do this add the following line to the 'component.mk' files of your project:
`CFLAGS += --coverage`
It will enable coverage info for all source files of your component. If you need to enable the option only for certain files you need to add the following line for every file of interest:
It will enable coverage info for all source files of your component. If you need to enable the option only for certain files the following line should be added for every file of interest:
`gcov_example.o: CFLAGS += --coverage`
Replace `gcov_example.o` with path to your file.
3. Add call to `esp_gcov_dump` function in your program. This function will wait for command from the host and dump coverage data. The exact place where to put call to `esp_gcov_dump` depends on the program.
Usually it should be placed at the end of the program execution (at exit). See `gcov_example.c` for example.
4. Build, flash and run program.
5. Wait until `esp_gcov_dump` is called. To detect this a call to `printf` can be used (see `gcov_example.c`) or, for example, you can use a LED to indicate the readiness to dump data.
6. Connect OpenOCD to the target and start telnet session with it.
7. Run the following OpenOCD command:
`esp32 gcov`
Example of the command output:
### Hard-coded Dump Call
This method requires `esp_gcov_dump` to be called from your application's code. Below are additional steps which should be performed after the generic ones to obtain coverage info via hard-coded call. Step 1 is already done for this example project.
1. Add call to `esp_gcov_dump` function in your program. This function will wait for command from host and dump coverage data. The exact place where to put the call depends on the program.
Usually it should be placed at the end of the program execution (at exit). But if you need to generate GCOV data incrementally `esp_gcov_dump` can be called multiple times. See `gcov_example.c` for example.
2. Build, flash and run program.
3. Wait until `esp_gcov_dump` is called. To detect this a call to `printf` can be used (see `gcov_example.c`) or, for example, you can use a LED to indicate the readiness to dump data.
Another way to detect call to `esp_gcov_dump` is to set breakpoint on that function, start target execution and wait for the target to be stopped. See the next section for respective GDB example.
4. Connect OpenOCD to the target and start telnet session with it.
5. Run the following OpenOCD command: `esp32 gcov dump`
Example of the command output:
```
> esp32 gcov dump
Total trace memory: 16384 bytes
Connect targets...
Target halted. PRO_CPU: PC=0x40088BC3 (active) APP_CPU: PC=0x400D14E6
Targets connected.
Open file 0x1 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file 0x1 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file 0x2 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Open file 0x2 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Disconnect targets...
Target halted. PRO_CPU: PC=0x400D14E6 (active) APP_CPU: PC=0x400D14E6
Targets disconnected.
```
#### Dump Using GDB
As it was said above breakpoint can be used to detect when `esp_gcov_dump` is called.
The following GDB commands can be used to dump data upon call to `esp_gcov_dump` automatically (you can put them into `gdbinit` file):
```
b esp_gcov_dump
commands
mon esp32 gcov dump
end
```
Note that all OpenOCD commands should be invoked in gdb as: `mon <oocd_command>`.
### Instant Run-Time Dump
Instant dump does not require to call `esp_gcov_dump`, so your application's code does not need to be modified. This method stops target at its current state and executes builtin IDF gcov debug stub function.
Having data dumped target resumes its execution. Below are the steps which should be performed to do instant dump. Step 1 is already done for this example project.
1. Enable OpenOCD debug stubs in menuconfig `Component config -> ESP32-specific -> OpenOCD debug stubs`.
2. Build, flash and run program.
3. Connect OpenOCD to the target and start telnet session with it.
4. Run the following OpenOCD command: `esp32 gcov`
Example of the command output:
```
> esp32 gcov
Total trace memory: 16384 bytes
Connect targets...
Target halted. PRO_CPU: PC=0x400D0CDC (active) APP_CPU: PC=0x00000000
esp32: target state: halted
Resume targets
Target halted. PRO_CPU: PC=0x400D14DA (active) APP_CPU: PC=0x400D14DA
Targets connected.
Open file '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Open file '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Open file 0x1 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file 0x1 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example.gcda'
Open file 0x2 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Open file 0x2 '/home/alexey/projects/esp/esp-idf/examples/system/gcov/build/main/gcov_example_func.gcda'
Target halted. PRO_CPU: PC=0x400844CE (active) APP_CPU: PC=0x400855E3
Disconnect targets...
Target halted. PRO_CPU: PC=0x400D17CA (active) APP_CPU: PC=0x400D0CDC
esp32: target state: halted
Resume targets
Targets disconnected.
>
```
As shown in the output above there can be errors reported. This is because GCOV code tries to open non-existing coverage data files for reading before writing to them. It is normal situation and actually is not an error.
GCOV will save coverage data for every source file in directories for corresponding object files, usually under root build directory `build`.
### Coverage Data Accumulation
Coverage data from several dumps are automatically accumulated. So the resulting gcov data files contain statistics since the board reset. Every data dump updates files accordingly.
New data collection is started if target has been reset.
## How To Process Coverage Info

View File

@ -20,9 +20,11 @@
void blink_dummy_func(void);
void blink_task(void *pvParameter)
static void blink_task(void *pvParameter)
{
int dump_gcov_after = 2;
// The first two iterations GCOV data are dumped using call to esp_gcov_dump() and OOCD's "esp32 gcov dump" command.
// After that they can be dumped using OOCD's "esp32 gcov" command only.
int dump_gcov_after = -2;
/* Configure the IOMUX register for pad BLINK_GPIO (some pads are
muxed to GPIO on reset already, but some default to other
functions and need to be switched to GPIO. Consult the
@ -32,21 +34,22 @@ void blink_task(void *pvParameter)
gpio_pad_select_gpio(BLINK_GPIO);
/* Set the GPIO as a push/pull output */
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
while(dump_gcov_after-- > 0) {
while(1) {
/* Blink off (output low) */
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelay(500 / portTICK_PERIOD_MS);
/* Blink on (output high) */
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelay(500 / portTICK_PERIOD_MS);
blink_dummy_func();
if (dump_gcov_after++ < 0) {
// Dump gcov data
printf("Ready to dump GCOV data...\n");
esp_gcov_dump();
printf("GCOV data have been dumped.\n");
}
}
// Dump gcov data
printf("Ready to dump GCOV data...\n");
esp_gcov_dump();
printf("GCOV data have been dumped.\n");
while(1);
}
void app_main()