From c1b6a37bb1287dc5e1767ea2647e11db3df9dd6c Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Thu, 15 Feb 2018 20:09:03 +0300 Subject: [PATCH] esp32: Adds GCOV debug stubs support Adds the following functionality - Debug stubs infrastructure - Stub for retrieveing GCOV data without user source code modification --- components/app_trace/Kconfig | 8 ++ components/app_trace/app_trace.c | 60 +++++++++++- components/app_trace/gcov/gcov_rtio.c | 115 ++++++++++++++++++++--- components/app_trace/host_file_io.c | 47 +++++---- components/esp32/Kconfig | 7 ++ components/esp32/cpu_start.c | 4 + components/esp32/dbg_stubs.c | 95 +++++++++++++++++++ components/esp32/include/esp_dbg_stubs.h | 50 ++++++++++ examples/system/gcov/README.md | 99 +++++++++++++------ examples/system/gcov/main/gcov_example.c | 25 ++--- 10 files changed, 437 insertions(+), 73 deletions(-) create mode 100644 components/esp32/dbg_stubs.c create mode 100644 components/esp32/include/esp_dbg_stubs.h diff --git a/components/app_trace/Kconfig b/components/app_trace/Kconfig index 9426c7539..d80331996 100644 --- a/components/app_trace/Kconfig +++ b/components/app_trace/Kconfig @@ -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 diff --git a/components/app_trace/app_trace.c b/components/app_trace/app_trace.c index d5a56471b..5552fe5e2 100644 --- a/components/app_trace/app_trace.c +++ b/components/app_trace/app_trace.c @@ -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 diff --git a/components/app_trace/gcov/gcov_rtio.c b/components/app_trace/gcov/gcov_rtio.c index 5e0b7e913..e53eb6431 100644 --- a/components/app_trace/gcov/gcov_rtio.c +++ b/components/app_trace/gcov/gcov_rtio.c @@ -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 diff --git a/components/app_trace/host_file_io.c b/components/app_trace/host_file_io.c index d3cf2447b..a6dd35c08 100644 --- a/components/app_trace/host_file_io.c +++ b/components/app_trace/host_file_io.c @@ -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; } diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 3c94b0e95..fcdb01910 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -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" diff --git a/components/esp32/cpu_start.c b/components/esp32/cpu_start.c index 0454e3ac2..4d907f333 100644 --- a/components/esp32/cpu_start.c +++ b/components/esp32/cpu_start.c @@ -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!"); diff --git a/components/esp32/dbg_stubs.c b/components/esp32/dbg_stubs.c new file mode 100644 index 000000000..51e9749b0 --- /dev/null +++ b/components/esp32/dbg_stubs.c @@ -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 diff --git a/components/esp32/include/esp_dbg_stubs.h b/components/esp32/include/esp_dbg_stubs.h new file mode 100644 index 000000000..899dfa56e --- /dev/null +++ b/components/esp32/include/esp_dbg_stubs.h @@ -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_ \ No newline at end of file diff --git a/examples/system/gcov/README.md b/examples/system/gcov/README.md index effc75b2f..5eefed2d5 100644 --- a/examples/system/gcov/README.md +++ b/examples/system/gcov/README.md @@ -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 `. + +### 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 diff --git a/examples/system/gcov/main/gcov_example.c b/examples/system/gcov/main/gcov_example.c index c323886ef..1cfa9ea53 100644 --- a/examples/system/gcov/main/gcov_example.c +++ b/examples/system/gcov/main/gcov_example.c @@ -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()