feat(gdbstub): modify GDB stub for ESP8266

This commit is contained in:
dongheng
2019-10-28 15:47:13 +08:00
committed by Dong Heng
parent 56747578d2
commit 1e69cbd242
19 changed files with 272 additions and 78 deletions

View File

@ -178,6 +178,15 @@ config ESP_PANIC_SILENT_REBOOT
bool "Silent reboot"
help
Just resets the processor without outputting anything
config ESP_PANIC_GDBSTUB
bool "Invoke GDBStub"
select ESP_GDBSTUB_ENABLED
help
Invoke gdbstub on the serial port, allowing for gdb to attach to it to do a postmortem
of the crash.
The UART's baudrate should be 115200 or others which can be recognized by xtensa GDB.
endchoice
config MAIN_TASK_STACK_SIZE

View File

@ -8,6 +8,5 @@ set(esp_gdbstub_srcs "src/gdbstub.c"
idf_component_register(SRCS "${esp_gdbstub_srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private_include" "${target}" "xtensa"
LDFRAGMENTS "linker.lf"
REQUIRES "freertos"
PRIV_REQUIRES "soc" "xtensa" "esp_rom")
PRIV_REQUIRES "esp8266")

View File

@ -1,4 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := private_include esp32 xtensa
COMPONENT_SRCDIRS := src esp32 xtensa
COMPONENT_ADD_LDFRAGMENTS += linker.lf
COMPONENT_PRIV_INCLUDEDIRS := private_include esp8266 xtensa
COMPONENT_SRCDIRS := src esp8266 xtensa

View File

@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "soc/uart_periph.h"
#include "soc/gpio_periph.h"
#include "esp_gdbstub_common.h"
#include "esp8266/uart_register.h"
#include "esp8266/eagle_soc.h"
#include "sdkconfig.h"
#include "esp_task_wdt.h"
#define UART_NUM CONFIG_CONSOLE_UART_NUM
@ -25,23 +25,24 @@ void esp_gdbstub_target_init(void)
int esp_gdbstub_getchar(void)
{
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_RXFIFO_CNT) == 0) {
;
while (((READ_PERI_REG(UART_STATUS(UART_NUM)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT) == 0) {
esp_task_wdt_reset();
}
return REG_READ(UART_FIFO_REG(UART_NUM));
return READ_PERI_REG(UART_FIFO(UART_NUM));
}
void esp_gdbstub_putchar(int c)
{
while (REG_GET_FIELD(UART_STATUS_REG(UART_NUM), UART_TXFIFO_CNT) >= 126) {
while (((READ_PERI_REG(UART_STATUS(UART_NUM)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) >= 126) {
;
}
REG_WRITE(UART_FIFO_REG(UART_NUM), c);
WRITE_PERI_REG(UART_FIFO(UART_NUM) , c);
}
int esp_gdbstub_readmem(intptr_t addr)
{
if (addr < 0x20000000 || addr >= 0x80000000) {
if (addr < 0x3ff00000 || addr >= 0x60010000) {
/* see cpu_configure_region_protection */
return -1;
}

View File

@ -1,7 +0,0 @@
[mapping:esp_gdbstub]
archive: libesp_gdbstub.a
entries:
if ESP32_PANIC_HANDLER_IRAM = y:
* (noflash_text)
else:
* (default)

View File

@ -25,8 +25,8 @@ typedef XtExcFrame esp_gdbstub_frame_t;
/* GDB regfile structure, configuration dependent */
typedef struct {
uint32_t pc;
uint32_t a[XCHAL_NUM_AREGS];
uint32_t pc;
#if XCHAL_HAVE_LOOPS
uint32_t lbeg;
@ -36,6 +36,8 @@ typedef struct {
uint32_t sar;
uint32_t litbase;
#if XCHAL_HAVE_WINDOWED
uint32_t windowbase;
uint32_t windowstart;

View File

@ -15,13 +15,14 @@
#include <string.h>
#include "esp_gdbstub.h"
#include "esp_gdbstub_common.h"
#include "soc/cpu.h"
#include "soc/soc_memory_layout.h"
#include "esp8266/eagle_soc.h"
#include "sdkconfig.h"
#if !XCHAL_HAVE_WINDOWED
#warning "gdbstub_xtensa: revisit the implementation for Call0 ABI"
#endif
inline static bool esp_stack_ptr_is_sane(uint32_t sp)
{
//Check if stack ptr is in between SOC_DRAM_LOW and SOC_DRAM_HIGH, and 16 byte aligned.
return !(!IS_DRAM(sp) || ((sp & 0xF) != 0));
}
static void init_regfile(esp_gdbstub_gdb_regfile_t *dst)
{
@ -36,10 +37,12 @@ static void update_regfile_common(esp_gdbstub_gdb_regfile_t *dst)
if (!esp_stack_ptr_is_sane(dst->a[1])) {
dst->a[1] = 0xDEADBEEF;
}
#if XCHAL_HAVE_WINDOWED
dst->windowbase = 0;
dst->windowstart = 0x1;
RSR(CONFIGID0, dst->configid0);
RSR(CONFIGID1, dst->configid1);
#endif
}
void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_gdb_regfile_t *dst)
@ -51,9 +54,11 @@ void esp_gdbstub_frame_to_regfile(const esp_gdbstub_frame_t *frame, esp_gdbstub_
for (int i = 0; i < 16; i++) {
dst->a[i] = a_regs[i];
}
#if XCHAL_HAVE_WINDOWED
for (int i = 16; i < 64; i++) {
dst->a[i] = 0xDEADBEEF;
}
#endif
#if XCHAL_HAVE_LOOPS
dst->lbeg = frame->lbeg;
@ -78,9 +83,11 @@ static void solicited_frame_to_regfile(const XtSolFrame *frame, esp_gdbstub_gdb_
for (int i = 0; i < 4; i++) {
dst->a[i] = a_regs[i];
}
#if XCHAL_HAVE_WINDOWED
for (int i = 4; i < 64; i++) {
dst->a[i] = 0xDEADBEEF;
}
#endif
dst->ps = (frame->ps & PS_UM) ? (frame->ps & ~PS_EXCM) : frame->ps;
update_regfile_common(dst);

View File

@ -5098,6 +5098,89 @@ const TickType_t xConstTickCount = xTickCount;
#endif /* INCLUDE_vTaskSuspend */
}
#if ( configENABLE_TASK_SNAPSHOT == 1 )
static void prvTaskGetSnapshot( TaskSnapshot_t *pxTaskSnapshotArray, UBaseType_t *uxTask, TCB_t *pxTCB )
{
if (pxTCB == NULL) {
return;
}
pxTaskSnapshotArray[ *uxTask ].pxTCB = pxTCB;
pxTaskSnapshotArray[ *uxTask ].pxTopOfStack = (StackType_t *)pxTCB->pxTopOfStack;
#if( portSTACK_GROWTH < 0 )
{
pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxTCB->pxEndOfStack;
}
#else
{
pxTaskSnapshotArray[ *uxTask ].pxEndOfStack = pxTCB->pxStack;
}
#endif
(*uxTask)++;
}
static void prvTaskGetSnapshotsFromList( TaskSnapshot_t *pxTaskSnapshotArray, UBaseType_t *uxTask, const UBaseType_t uxArraySize, List_t *pxList )
{
TCB_t *pxNextTCB, *pxFirstTCB;
if( listCURRENT_LIST_LENGTH( pxList ) > ( UBaseType_t ) 0 )
{
listGET_OWNER_OF_NEXT_ENTRY( pxFirstTCB, pxList );
do
{
if( *uxTask >= uxArraySize )
break;
listGET_OWNER_OF_NEXT_ENTRY( pxNextTCB, pxList );
prvTaskGetSnapshot( pxTaskSnapshotArray, uxTask, pxNextTCB );
} while( pxNextTCB != pxFirstTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz )
{
UBaseType_t uxTask = 0, i = 0;
*pxTcbSz = sizeof(TCB_t);
/* Fill in an TaskStatus_t structure with information on each
task in the Ready state. */
i = configMAX_PRIORITIES;
do
{
i--;
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &( pxReadyTasksLists[ i ] ) );
} while( i > ( UBaseType_t ) tskIDLE_PRIORITY ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
/* Fill in an TaskStatus_t structure with information on each
task in the Blocked state. */
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxDelayedTaskList );
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, ( List_t * ) pxOverflowDelayedTaskList );
for (i = 0; i < portNUM_PROCESSORS; i++) {
if( uxTask >= uxArraySize )
break;
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xPendingReadyList );
}
#if( INCLUDE_vTaskDelete == 1 )
{
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xTasksWaitingTermination );
}
#endif
#if ( INCLUDE_vTaskSuspend == 1 )
{
prvTaskGetSnapshotsFromList( pxTaskSnapshotArray, &uxTask, uxArraySize, &xSuspendedTaskList );
}
#endif
return uxTask;
}
#endif
/* Code below here allows additional code to be inserted into this source file,
especially where access to file scope functions and data is needed (for example
when performing module tests). */

View File

@ -2359,6 +2359,28 @@ void *pvTaskIncrementMutexHeldCount( void ) PRIVILEGED_FUNCTION;
*/
void vTaskInternalSetTimeOutState( TimeOut_t * const pxTimeOut ) PRIVILEGED_FUNCTION;
/**
* Used with the uxTaskGetSnapshotAll() function to save memory snapshot of each task in the system.
* We need this struct because TCB_t is defined (hidden) in tasks.c.
*/
typedef struct xTASK_SNAPSHOT
{
void *pxTCB; /*!< Address of task control block. */
StackType_t *pxTopOfStack; /*!< Points to the location of the last item placed on the tasks stack. */
StackType_t *pxEndOfStack; /*!< Points to the end of the stack. pxTopOfStack < pxEndOfStack, stack grows hi2lo
pxTopOfStack > pxEndOfStack, stack grows lo2hi*/
} TaskSnapshot_t;
/*
* This function fills array with TaskSnapshot_t structures for every task in the system.
* Used by core dump facility to get snapshots of all tasks in the system.
* Only available when configENABLE_TASK_SNAPSHOT is set to 1.
* @param pxTaskSnapshotArray Pointer to array of TaskSnapshot_t structures to store tasks snapshot data.
* @param uxArraySize Size of tasks snapshots array.
* @param pxTcbSz Pointer to store size of TCB.
* @return Number of elements stored in array.
*/
UBaseType_t uxTaskGetSnapshotAll( TaskSnapshot_t * const pxTaskSnapshotArray, const UBaseType_t uxArraySize, UBaseType_t * const pxTcbSz );
#ifdef __cplusplus
}

View File

@ -182,5 +182,9 @@ uint32_t esp_get_time(void);
#define configIDLE_TASK_STACK_SIZE CONFIG_FREERTOS_IDLE_TASK_STACKSIZE
#endif /* configIDLE_TASK_STACK_SIZE */
#ifndef configENABLE_TASK_SNAPSHOT
#define configENABLE_TASK_SNAPSHOT 1
#endif
#endif /* FREERTOS_CONFIG_H */

View File

@ -63,22 +63,25 @@ extern "C" {
*/
/* Type definitions. */
#define portCHAR char
#define portCHAR int8_t
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE unsigned char
#define portBASE_TYPE long
#define portLONG int32_t
#define portSHORT int16_t
#define portSTACK_TYPE uint8_t
#define portBASE_TYPE int
#define BaseType_t portBASE_TYPE
#define TickType_t unsigned portLONG
#define UBaseType_t unsigned portBASE_TYPE
#define StackType_t portSTACK_TYPE
typedef portSTACK_TYPE StackType_t;
typedef portBASE_TYPE BaseType_t;
typedef unsigned portBASE_TYPE UBaseType_t;
typedef unsigned portLONG portTickType;
typedef unsigned int INT32U;
#define portMAX_DELAY ( portTickType ) 0xffffffff
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif
/*-----------------------------------------------------------*/
/* Architecture specifics. */
@ -131,6 +134,7 @@ void PortEnableInt_NoNest( void );
#define portEXIT_CRITICAL() vPortExitCritical()
#define xPortGetCoreID() 0
#define xTaskGetCurrentTaskHandleForCPU(_cpu) xTaskGetCurrentTaskHandle()
// no need to disable/enable lvl1 isr again in ISR
//#define portSET_INTERRUPT_MASK_FROM_ISR() PortDisableInt_NoNest()

View File

@ -30,6 +30,7 @@ NOTE: The Xtensa architecture requires stack pointer alignment to 16 bytes.
#include <xtensa/config/tie.h>
#include <xtensa/corebits.h>
#include <xtensa/config/system.h>
#include <xtensa/xtruntime-frames.h>
/*
@ -79,6 +80,52 @@ space to help manage the spilling of the register windows.
#define XT_STK_SAR 0x4C
#define XT_STK_EXCCAUSE 0x50
#define XT_STK_EXCVADDR 0x54
STRUCT_BEGIN
STRUCT_FIELD (long, 4, XT_STK_EXIT, exit) /* exit point for dispatch */
STRUCT_FIELD (long, 4, XT_STK_PC, pc) /* return PC */
STRUCT_FIELD (long, 4, XT_STK_PS, ps) /* return PS */
STRUCT_FIELD (long, 4, XT_STK_A0, a0)
STRUCT_FIELD (long, 4, XT_STK_A1, a1) /* stack pointer before interrupt */
STRUCT_FIELD (long, 4, XT_STK_A2, a2)
STRUCT_FIELD (long, 4, XT_STK_A3, a3)
STRUCT_FIELD (long, 4, XT_STK_A4, a4)
STRUCT_FIELD (long, 4, XT_STK_A5, a5)
STRUCT_FIELD (long, 4, XT_STK_A6, a6)
STRUCT_FIELD (long, 4, XT_STK_A7, a7)
STRUCT_FIELD (long, 4, XT_STK_A8, a8)
STRUCT_FIELD (long, 4, XT_STK_A9, a9)
STRUCT_FIELD (long, 4, XT_STK_A10, a10)
STRUCT_FIELD (long, 4, XT_STK_A11, a11)
STRUCT_FIELD (long, 4, XT_STK_A12, a12)
STRUCT_FIELD (long, 4, XT_STK_A13, a13)
STRUCT_FIELD (long, 4, XT_STK_A14, a14)
STRUCT_FIELD (long, 4, XT_STK_A15, a15)
STRUCT_FIELD (long, 4, XT_STK_SAR, sar)
STRUCT_FIELD (long, 4, XT_STK_EXCCAUSE, exccause)
STRUCT_FIELD (long, 4, XT_STK_EXCVADDR, excvaddr)
#if XCHAL_HAVE_LOOPS
STRUCT_FIELD (long, 4, XT_STK_LBEG, lbeg)
STRUCT_FIELD (long, 4, XT_STK_LEND, lend)
STRUCT_FIELD (long, 4, XT_STK_LCOUNT, lcount)
#endif
#ifndef __XTENSA_CALL0_ABI__
/* Temporary space for saving stuff during window spill */
STRUCT_FIELD (long, 4, XT_STK_TMP0, tmp0)
STRUCT_FIELD (long, 4, XT_STK_TMP1, tmp1)
STRUCT_FIELD (long, 4, XT_STK_TMP2, tmp2)
#endif
#ifdef XT_USE_SWPRI
/* Storage for virtual priority mask */
STRUCT_FIELD (long, 4, XT_STK_VPRI, vpri)
#endif
#ifdef XT_USE_OVLY
/* Storage for overlay state */
STRUCT_FIELD (long, 4, XT_STK_OVLY, ovly)
#endif
STRUCT_END(XtExcFrame)
#if XCHAL_HAVE_LOOPS
#define XT_STK_LBEG 0x50
#define XT_STK_LEND 0x54
@ -115,6 +162,49 @@ Windowed -
#endif
/*
-------------------------------------------------------------------------------
SOLICITED STACK FRAME FOR A THREAD
A stack frame of this structure is allocated whenever a thread enters the
RTOS kernel intentionally (and synchronously) to submit to thread scheduling.
It goes on the current thread's stack.
The solicited frame only includes registers that are required to be preserved
by the callee according to the compiler's ABI conventions, some space to save
the return address for returning to the caller, and the caller's PS register.
For Windowed ABI, this stack frame includes the caller's base save area.
Note on XT_SOL_EXIT field:
It is necessary to distinguish a solicited from an interrupt stack frame.
This field corresponds to XT_STK_EXIT in the interrupt stack frame and is
always at the same offset (0). It can be written with a code (usually 0)
to distinguish a solicted frame from an interrupt frame. An RTOS port may
opt to ignore this field if it has another way of distinguishing frames.
-------------------------------------------------------------------------------
*/
STRUCT_BEGIN
#ifdef __XTENSA_CALL0_ABI__
STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit)
STRUCT_FIELD (long, 4, XT_SOL_PC, pc)
STRUCT_FIELD (long, 4, XT_SOL_PS, ps)
STRUCT_FIELD (long, 4, XT_SOL_NEXT, a0)
STRUCT_FIELD (long, 4, XT_SOL_A12, a1) /* should be on 16-byte alignment */
STRUCT_FIELD (long, 4, XT_SOL_A13, a2)
#else
STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit)
STRUCT_FIELD (long, 4, XT_SOL_PC, pc)
STRUCT_FIELD (long, 4, XT_SOL_PS, ps)
STRUCT_FIELD (long, 4, XT_SOL_NEXT, next)
STRUCT_FIELD (long, 4, XT_SOL_A0, a0) /* should be on 16-byte alignment */
STRUCT_FIELD (long, 4, XT_SOL_A1, a1)
STRUCT_FIELD (long, 4, XT_SOL_A2, a2)
STRUCT_FIELD (long, 4, XT_SOL_A3, a3)
#endif
STRUCT_END(XtSolFrame)
/*******************************************************************************
SOLICTED STACK FRAME FOR A THREAD

View File

@ -27,6 +27,7 @@
#include "esp_err.h"
#include "FreeRTOS.h"
#include "freertos/xtensa_context.h"
#define PANIC(_fmt, ...) ets_printf(_fmt, ##__VA_ARGS__)
@ -42,39 +43,14 @@
#define ESP_PANIC_PRINT
#endif
#if defined(CONFIG_ESP_PANIC_GDBSTUB)
#define ESP_PANIC_GDBSTUB
#else
#undef ESP_PANIC_GDBSTUB
#endif
#ifdef ESP_PANIC_PRINT
typedef struct panic_frame {
uint32_t exit;
uint32_t pc;
uint32_t ps;
uint32_t a0;
uint32_t a1;
uint32_t a2;
uint32_t a3;
uint32_t a4;
uint32_t a5;
uint32_t a6;
uint32_t a7;
uint32_t a8;
uint32_t a9;
uint32_t a10;
uint32_t a11;
uint32_t a12;
uint32_t a13;
uint32_t a14;
uint32_t a15;
uint32_t sar;
uint32_t exccause;
} panic_frame_t;
static void panic_frame(panic_frame_t *frame)
static inline void panic_frame(XtExcFrame *frame)
{
static const char *sdesc[] = {
"PC", "PS", "A0", "A1",
@ -161,6 +137,11 @@ void panicHandler(void *frame, int wdt)
#ifdef ESP_PANIC_REBOOT
esp_panic_reset();
#elif defined(ESP_PANIC_GDBSTUB)
extern void esp_gdbstub_panic_handler(void *frame);
PANIC("Entering gdb stub now.\r\n");
esp_gdbstub_panic_handler(frame);
#else
while (1) {
esp_task_wdt_reset();

View File

@ -78,7 +78,7 @@ uint8_t *__cpu_init_stk(uint8_t *stack_top, void (*_entry)(void *), void *param,
uint32_t *sp, *tp, *stk = (uint32_t *)stack_top;
/* Create interrupt stack frame aligned to 16 byte boundary */
sp = (uint32_t *)(((INT32U)(stk + 1) - XT_CP_SIZE - XT_STK_FRMSZ) & ~0xf);
sp = (uint32_t *)(((uint32_t)(stk + 1) - XT_CP_SIZE - XT_STK_FRMSZ) & ~0xf);
/* Clear the entire frame (do not use memset() because we don't depend on C library) */
for (tp = sp; tp <= stk; ++tp) {
@ -88,7 +88,7 @@ uint8_t *__cpu_init_stk(uint8_t *stack_top, void (*_entry)(void *), void *param,
/* Explicitly initialize certain saved registers */
SET_STKREG(XT_STK_PC, _entry); /* task entrypoint */
SET_STKREG(XT_STK_A0, _exit); /* to terminate GDB backtrace */
SET_STKREG(XT_STK_A1, (INT32U)sp + XT_STK_FRMSZ); /* physical top of stack frame */
SET_STKREG(XT_STK_A1, (uint32_t)sp + XT_STK_FRMSZ); /* physical top of stack frame */
SET_STKREG(XT_STK_A2, param); /* parameters */
SET_STKREG(XT_STK_EXIT, _xt_user_exit); /* user exception exit dispatcher */

View File

@ -446,7 +446,7 @@ void pthread_exit(void *value_ptr)
}
xSemaphoreGive(s_threads_mux);
ESP_LOGD(TAG, "Task stk_wm = %ld", uxTaskGetStackHighWaterMark(NULL));
ESP_LOGD(TAG, "Task stk_wm = %u", uxTaskGetStackHighWaterMark(NULL));
if (detached) {
vTaskDelete(NULL);

View File

@ -285,7 +285,7 @@ void aws_iot_task(void *param) {
continue;
}
ESP_LOGI(TAG, "Stack remaining for task '%s' is %lu bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));
ESP_LOGI(TAG, "Stack remaining for task '%s' is %u bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));
vTaskDelay(1000 / portTICK_RATE_MS);
sprintf(cPayload, "%s : %d ", "hello from ESP32 (QOS0)", i++);
paramsQOS0.payloadLen = strlen(cPayload);

View File

@ -324,7 +324,7 @@ void aws_iot_task(void *param) {
}
}
ESP_LOGI(TAG, "*****************************************************************************************");
ESP_LOGI(TAG, "Stack remaining for task '%s' is %lu bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));
ESP_LOGI(TAG, "Stack remaining for task '%s' is %u bytes", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL));
ESP_LOGI(TAG, "Free memory for application is %d bytes", esp_get_free_heap_size());

View File

@ -22,7 +22,7 @@ struct async_resp_arg {
esp_err_t hello_get_handler(httpd_req_t *req)
{
#define STR "Hello World!"
ESP_LOGI(TAG, "Free Stack for server task: '%ld'", uxTaskGetStackHighWaterMark(NULL));
ESP_LOGI(TAG, "Free Stack for server task: '%u'", uxTaskGetStackHighWaterMark(NULL));
httpd_resp_send(req, STR, strlen(STR));
return ESP_OK;
#undef STR