coredump: change data format to ELF

This commit is contained in:
Alexey Gerenkov 2019-11-22 13:25:43 +08:00 committed by Angus Gratton
parent 7eb89ae868
commit 27ce4d13df
18 changed files with 3911 additions and 1203 deletions

View file

@ -1,8 +1,9 @@
idf_component_register(SRCS "src/core_dump_common.c"
idf_component_register(SRCS "src/core_dump_common.c"
"src/core_dump_flash.c"
"src/core_dump_port.c"
"src/core_dump_uart.c"
"src/core_dump_elf.c"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "include_core_dump"
LDFRAGMENTS linker.lf
PRIV_REQUIRES spi_flash soc)
PRIV_REQUIRES spi_flash app_update mbedtls esp_rom soc)

View file

@ -20,6 +20,35 @@ menu "Core dump"
bool "None"
endchoice
choice ESP32_COREDUMP_DATA_FORMAT
prompt "Core dump data format"
default ESP32_COREDUMP_DATA_FORMAT_ELF
depends on !ESP32_ENABLE_COREDUMP_TO_NONE
help
Select the data format for core dump.
config ESP32_COREDUMP_DATA_FORMAT_BIN
bool "Binary format"
select ESP32_ENABLE_COREDUMP
config ESP32_COREDUMP_DATA_FORMAT_ELF
bool "ELF format"
select ESP32_ENABLE_COREDUMP
endchoice
choice ESP32_COREDUMP_CHECKSUM
prompt "Core dump data integrity check"
default ESP32_COREDUMP_CHECKSUM_CRC32
depends on !ESP32_ENABLE_COREDUMP_TO_NONE
help
Select the integrity check for the core dump.
config ESP32_COREDUMP_CHECKSUM_CRC32
bool "Use CRC32 for integrity verification"
select ESP32_ENABLE_COREDUMP
config ESP32_COREDUMP_CHECKSUM_SHA256
bool "Use SHA256 for integrity verification"
select ESP32_ENABLE_COREDUMP
depends on ESP32_COREDUMP_DATA_FORMAT_ELF
endchoice
config ESP32_ENABLE_COREDUMP
bool
default F
@ -41,5 +70,15 @@ menu "Core dump"
Config delay (in ms) before printing core dump to UART.
Delay can be interrupted by pressing Enter key.
config ESP32_CORE_DUMP_STACK_SIZE
int "Reserved stack size"
depends on ESP32_ENABLE_COREDUMP
default 0
help
Size of the memory to be reserved for core dump stack. If 0 core dump process will run on
the stack of crashed task/ISR, otherwise special stack will be allocated.
To ensure that core dump itself will not overflow task/ISR stack set this to the value above 800.
NOTE: It eats DRAM.
endmenu

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2019 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.
@ -70,7 +70,6 @@ void esp_core_dump_to_flash(XtExcFrame *frame);
*/
void esp_core_dump_to_uart(XtExcFrame *frame);
/**************************************************************************************/
/*********************************** USER MODE API ************************************/
/**************************************************************************************/

View file

@ -0,0 +1,21 @@
// Copyright 2015-2019 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_CORE_DUMP_ELF_H_
#define ESP_CORE_DUMP_ELF_H_
#include "esp_core_dump_priv.h"
esp_err_t esp_core_dump_write_elf(void *frame, core_dump_write_config_t *write_cfg);
#endif

View file

@ -0,0 +1,359 @@
/****************************************************************************
****************************************************************************
***
*** This header was automatically generated from a Linux kernel header
*** of the same name, to make information necessary for userspace to
*** call into the kernel available to libc. It contains only constants,
*** structures, and macros generated from the original header, and thus,
*** contains no copyrightable information.
***
****************************************************************************
****************************************************************************/
#ifndef _LINUX_ELF_H
#define _LINUX_ELF_H
#include <stdint.h>
#ifndef elf_read_implies_exec
#define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
#endif
typedef uint32_t Elf32_Addr;
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Off;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf32_Word;
typedef uint64_t Elf64_Addr;
typedef uint16_t Elf64_Half;
typedef int16_t Elf64_SHalf;
typedef uint64_t Elf64_Off;
typedef int32_t Elf64_Sword;
typedef uint32_t Elf64_Word;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;
#define PT_NULL 0
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
#define PT_NOTE 4
#define PT_SHLIB 5
#define PT_PHDR 6
#define PT_TLS 7
#define PT_LOOS 0x60000000
#define PT_HIOS 0x6fffffff
#define PT_LOPROC 0x70000000
#define PT_HIPROC 0x7fffffff
#define PT_GNU_EH_FRAME 0x6474e550
#define PT_GNU_STACK (PT_LOOS + 0x474e551)
#define ET_NONE 0
#define ET_REL 1
#define ET_EXEC 2
#define ET_DYN 3
#define ET_CORE 4
#define ET_LOPROC 0xff00
#define ET_HIPROC 0xffff
#define DT_NULL 0
#define DT_NEEDED 1
#define DT_PLTRELSZ 2
#define DT_PLTGOT 3
#define DT_HASH 4
#define DT_STRTAB 5
#define DT_SYMTAB 6
#define DT_RELA 7
#define DT_RELASZ 8
#define DT_RELAENT 9
#define DT_STRSZ 10
#define DT_SYMENT 11
#define DT_INIT 12
#define DT_FINI 13
#define DT_SONAME 14
#define DT_RPATH 15
#define DT_SYMBOLIC 16
#define DT_REL 17
#define DT_RELSZ 18
#define DT_RELENT 19
#define DT_PLTREL 20
#define DT_DEBUG 21
#define DT_TEXTREL 22
#define DT_JMPREL 23
#define DT_LOPROC 0x70000000
#define DT_HIPROC 0x7fffffff
#define STB_LOCAL 0
#define STB_GLOBAL 1
#define STB_WEAK 2
#define STT_NOTYPE 0
#define STT_OBJECT 1
#define STT_FUNC 2
#define STT_SECTION 3
#define STT_FILE 4
#define STT_COMMON 5
#define STT_TLS 6
#define ELF_ST_BIND(x) ((x) >> 4)
#define ELF_ST_TYPE(x) (((unsigned int) x) & 0xf)
#define ELF32_ST_BIND(x) ELF_ST_BIND(x)
#define ELF32_ST_TYPE(x) ELF_ST_TYPE(x)
#define ELF64_ST_BIND(x) ELF_ST_BIND(x)
#define ELF64_ST_TYPE(x) ELF_ST_TYPE(x)
typedef struct dynamic{
Elf32_Sword d_tag;
union{
Elf32_Sword d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
typedef struct {
Elf64_Sxword d_tag;
union {
Elf64_Xword d_val;
Elf64_Addr d_ptr;
} d_un;
} Elf64_Dyn;
#define ELF32_R_SYM(x) ((x) >> 8)
#define ELF32_R_TYPE(x) ((x) & 0xff)
#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
typedef struct elf32_rel {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
typedef struct elf64_rel {
Elf64_Addr r_offset;
Elf64_Xword r_info;
} Elf64_Rel;
typedef struct elf32_rela{
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Sword r_addend;
} Elf32_Rela;
typedef struct elf64_rela {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
typedef struct elf32_sym{
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
typedef struct elf64_sym {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
#define EI_NIDENT 16
typedef struct elf32_hdr{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
typedef struct elf64_hdr {
unsigned char e_ident[16];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
#define PF_R 0x4
#define PF_W 0x2
#define PF_X 0x1
typedef struct elf32_phdr{
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
typedef struct elf64_phdr {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
#define SHT_NULL 0
#define SHT_PROGBITS 1
#define SHT_SYMTAB 2
#define SHT_STRTAB 3
#define SHT_RELA 4
#define SHT_HASH 5
#define SHT_DYNAMIC 6
#define SHT_NOTE 7
#define SHT_NOBITS 8
#define SHT_REL 9
#define SHT_SHLIB 10
#define SHT_DYNSYM 11
#define SHT_NUM 12
#define SHT_LOPROC 0x70000000
#define SHT_HIPROC 0x7fffffff
#define SHT_LOUSER 0x80000000
#define SHT_HIUSER 0xffffffff
#define SHF_WRITE 0x1
#define SHF_ALLOC 0x2
#define SHF_EXECINSTR 0x4
#define SHF_MASKPROC 0xf0000000
#define SHN_UNDEF 0
#define SHN_LORESERVE 0xff00
#define SHN_LOPROC 0xff00
#define SHN_HIPROC 0xff1f
#define SHN_ABS 0xfff1
#define SHN_COMMON 0xfff2
#define SHN_HIRESERVE 0xffff
typedef struct elf32_shdr{
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
typedef struct elf64_shdr {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
#define EI_MAG0 0
#define EI_MAG1 1
#define EI_MAG2 2
#define EI_MAG3 3
#define EI_CLASS 4
#define EI_DATA 5
#define EI_VERSION 6
#define EI_OSABI 7
#define EI_PAD 8
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'
#define ELFMAG "\177ELF"
#define SELFMAG 4
#define ELFCLASSNONE 0
#define ELFCLASS32 1
#define ELFCLASS64 2
#define ELFCLASSNUM 3
#define ELFDATANONE 0
#define ELFDATA2LSB 1
#define ELFDATA2MSB 2
#define EV_NONE 0
#define EV_CURRENT 1
#define EV_NUM 2
#define ELFOSABI_NONE 0
#define ELFOSABI_LINUX 3
#ifndef ELF_OSABI
#define ELF_OSABI ELFOSABI_NONE
#endif
#define NT_PRSTATUS 1
#define NT_PRFPREG 2
#define NT_PRPSINFO 3
#define NT_TASKSTRUCT 4
#define NT_AUXV 6
#define NT_PRXFPREG 0x46e62b7f
typedef struct elf32_note {
Elf32_Word n_namesz;
Elf32_Word n_descsz;
Elf32_Word n_type;
} Elf32_Nhdr;
typedef struct elf64_note {
Elf64_Word n_namesz;
Elf64_Word n_descsz;
Elf64_Word n_type;
} Elf64_Nhdr;
#if ELF_CLASS == ELFCLASS32
#define elfhdr Elf32_Ehdr
#define elf_phdr Elf32_Phdr
#define elf_note Elf32_Nhdr
#define elf_shdr Elf32_Shdr
#else
#define elfhdr Elf64_Ehdr
#define elf_phdr Elf64_Phdr
#define elf_note Elf64_Nhdr
#define elf_shdr Elf64_Shdr
#endif
#endif

View file

@ -0,0 +1,122 @@
// Copyright 2015-2019 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_CORE_DUMP_PORT_H_
#define ESP_CORE_DUMP_PORT_H_
#include "freertos/FreeRTOS.h"
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/crc.h"
#elif CONFIG_IDF_TARGET_ESP32S2BETA
#include "esp32s2beta/rom/crc.h"
#endif
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
#include "mbedtls/sha256.h"
#endif
#include "esp_core_dump_priv.h"
#include "soc/cpu.h"
#include "esp_debug_helpers.h"
#ifdef __cplusplus
extern "C" {
#endif
#define COREDUMP_TCB_SIZE sizeof(StaticTask_t)
// Gets RTOS tasks snapshot
uint32_t esp_core_dump_get_tasks_snapshot(core_dump_task_header_t* const tasks,
const uint32_t snapshot_size);
// Checks TCB consistency
bool esp_core_dump_tcb_addr_is_sane(uint32_t addr);
// Checks stack address
bool esp_core_dump_task_stack_end_is_sane(uint32_t sp);
bool esp_core_dump_mem_seg_is_sane(uint32_t addr, uint32_t sz);
void *esp_core_dump_get_current_task_handle(void);
bool esp_core_dump_check_task(void *frame, core_dump_task_header_t *task_snaphort, bool* is_current, bool* stack_is_valid);
bool esp_core_dump_check_stack(uint32_t stack_start, uint32_t stack_end);
uint32_t esp_core_dump_get_stack(core_dump_task_header_t* task_snapshot, uint32_t* stk_base, uint32_t* stk_len);
uint16_t esp_core_dump_get_arch_id(void);
uint32_t esp_core_dump_get_task_regs_dump(core_dump_task_header_t *task, void **reg_dump);
void esp_core_dump_init_extra_info(void);
uint32_t esp_core_dump_get_extra_info(void **info);
// Data integrity check functions
void esp_core_dump_checksum_init(core_dump_write_data_t* wr_data);
void esp_core_dump_checksum_update(core_dump_write_data_t* wr_data, void* data, size_t data_len);
size_t esp_core_dump_checksum_finish(core_dump_write_data_t* wr_data, void** chs_ptr);
#if CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
void esp_core_dump_print_sha256(const char* msg, const uint8_t* sha_output);
int esp_core_dump_sha(mbedtls_sha256_context *ctx,
const unsigned char *input, size_t ilen, unsigned char output[32]);
#endif
#define esp_core_dump_in_isr_context() xPortInterruptedFromISRContext()
uint32_t esp_core_dump_get_isr_stack_end(void);
#if CONFIG_ESP32_CORE_DUMP_STACK_SIZE > 0
#if LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG
// increase stack size in verbose mode
#define ESP32_CORE_DUMP_STACK_SIZE (CONFIG_ESP32_CORE_DUMP_STACK_SIZE+100)
#else
#define ESP32_CORE_DUMP_STACK_SIZE CONFIG_ESP32_CORE_DUMP_STACK_SIZE
#endif
#endif
void esp_core_dump_report_stack_usage(void);
#if ESP32_CORE_DUMP_STACK_SIZE > 0
#define COREDUMP_STACK_FILL_BYTE (0xa5U)
extern uint8_t s_coredump_stack[];
extern uint8_t *s_core_dump_sp;
#if LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG
#define esp_core_dump_fill_stack() \
memset(s_coredump_stack, COREDUMP_STACK_FILL_BYTE, ESP32_CORE_DUMP_STACK_SIZE)
#else
#define esp_core_dump_fill_stack()
#endif
#define esp_core_dump_setup_stack() \
{ \
s_core_dump_sp = (uint8_t *)((uint32_t)(s_coredump_stack + ESP32_CORE_DUMP_STACK_SIZE - 1) & ~0xf); \
esp_core_dump_fill_stack(); \
/* watchpoint 1 can be used for task stack overflow detection, re-use it, it is no more necessary */ \
esp_clear_watchpoint(1); \
esp_set_watchpoint(1, s_coredump_stack, 1, ESP_WATCHPOINT_STORE); \
asm volatile ("mov sp, %0" :: "r"(s_core_dump_sp)); \
ESP_COREDUMP_LOGD("Use core dump stack @ 0x%x", get_sp()); \
}
#else
#define esp_core_dump_setup_stack() \
{ \
/* if we are in ISR set watchpoint to the end of ISR stack */ \
if (xPortInterruptedFromISRContext()) { \
extern uint8_t port_IntStack; \
esp_clear_watchpoint(1); \
esp_set_watchpoint(1, &port_IntStack+xPortGetCoreID()*configISR_STACK_SIZE, 1, ESP_WATCHPOINT_STORE); \
} else { \
/* for tasks user should enable stack overflow detection in menuconfig
TODO: if not enabled in menuconfig enable it ourselves */ \
} \
}
#endif
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2019 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.
@ -14,10 +14,18 @@
#ifndef ESP_CORE_DUMP_H_
#define ESP_CORE_DUMP_H_
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "sdkconfig.h"
#if CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
// TODO: move this to portable part of the code
#include "mbedtls/sha256.h"
#endif
#define ESP_COREDUMP_LOG( level, format, ... ) if (LOG_LOCAL_LEVEL >= level) { ets_printf(DRAM_STR(format), esp_log_early_timestamp(), (const char *)TAG, ##__VA_ARGS__); }
#define ESP_COREDUMP_LOGE( format, ... ) ESP_COREDUMP_LOG(ESP_LOG_ERROR, LOG_FORMAT(E, format), ##__VA_ARGS__)
@ -33,18 +41,50 @@
#endif
#define COREDUMP_MAX_TASK_STACK_SIZE (64*1024)
#define COREDUMP_VERSION 1
#define COREDUMP_VERSION_BIN 1
#define COREDUMP_VERSION_ELF_CRC32 2
#define COREDUMP_VERSION_ELF_SHA256 3
#define COREDUMP_CURR_TASK_MARKER 0xDEADBEEF
#define COREDUMP_CURR_TASK_NOT_FOUND -1
typedef uint32_t core_dump_crc_t;
#if CONFIG_ESP32_ENABLE_COREDUMP
#if CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
#define COREDUMP_VERSION COREDUMP_VERSION_ELF_CRC32
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
#define COREDUMP_VERSION COREDUMP_VERSION_ELF_SHA256
#define COREDUMP_SHA256_LEN 32
#endif
#else
#define COREDUMP_VERSION COREDUMP_VERSION_BIN
#endif
typedef esp_err_t (*esp_core_dump_write_prepare_t)(void *priv, uint32_t *data_len);
typedef esp_err_t (*esp_core_dump_write_start_t)(void *priv);
typedef esp_err_t (*esp_core_dump_write_end_t)(void *priv);
typedef esp_err_t (*esp_core_dump_flash_write_data_t)(void *priv, void * data, uint32_t data_len);
/** core dump emitter control structure */
typedef uint32_t core_dump_crc_t;
typedef struct _core_dump_write_data_t
{
// TODO: move flash related data to flash-specific code
uint32_t off; // current offset in partition
union
{
uint8_t data8[4];
uint32_t data32;
} cached_data;
uint8_t cached_bytes;
#if CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
// TODO: move this to portable part of the code
mbedtls_sha256_context ctx;
char sha_output[COREDUMP_SHA256_LEN];
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
core_dump_crc_t crc; // CRC of dumped data
#endif
} core_dump_write_data_t;
// core dump emitter control structure
typedef struct _core_dump_write_config_t
{
// this function is called before core dump data writing
@ -69,37 +109,34 @@ typedef struct _core_dump_header_t
uint32_t version; // core dump struct version
uint32_t tasks_num; // number of tasks
uint32_t tcb_sz; // size of TCB
uint32_t mem_segs_num; // number of memory segments
} core_dump_header_t;
/** core dump task data header */
typedef struct _core_dump_task_header_t
{
void * tcb_addr; // TCB address
void* tcb_addr; // TCB address
uint32_t stack_start; // stack start address
uint32_t stack_end; // stack end address
} core_dump_task_header_t;
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
/** core dump memory segment header */
typedef struct _core_dump_mem_seg_header_t
{
uint32_t start; // memory region start address
uint32_t size; // memory region size
} core_dump_mem_seg_header_t;
// Core dump flash init function
void esp_core_dump_flash_init(void);
#endif
// Common core dump write function
void esp_core_dump_write(void *frame, core_dump_write_config_t *write_cfg);
// Gets RTOS tasks snapshot
uint32_t esp_core_dump_get_tasks_snapshot(core_dump_task_header_t* const tasks,
const uint32_t snapshot_size, uint32_t* const tcb_sz);
// Checks TCB consistency
bool esp_tcb_addr_is_sane(uint32_t addr, uint32_t sz);
bool esp_core_dump_process_tcb(void *frame, core_dump_task_header_t *task_snaphort, uint32_t tcb_sz);
bool esp_core_dump_process_stack(core_dump_task_header_t* task_snaphort, uint32_t *length);
#include "esp_core_dump_port.h"
#ifdef __cplusplus
}
#endif
#endif

View file

@ -6,6 +6,7 @@ entries:
core_dump_flash (noflash_text)
core_dump_common (noflash_text)
core_dump_port (noflash_text)
core_dump_elf (noflash_text)
else:
* (default)
@ -17,3 +18,14 @@ entries:
esp_flash_spi_init (noflash_text)
else:
* (default)
[mapping:mbedtls]
archive: libmbedtls.a
entries:
if ESP32_COREDUMP_CHECKSUM_SHA256 = y :
if MBEDTLS_HARDWARE_SHA = n:
sha256 (noflash_text)
else:
esp_sha256 (noflash_text)
else:
* (default)

View file

@ -13,145 +13,248 @@
// limitations under the License.
#include <string.h>
#include <stdbool.h>
#include "esp32/rom/crc.h"
#include "esp_debug_helpers.h"
#include "esp_partition.h"
#include "sdkconfig.h"
#include "esp_core_dump_priv.h"
#include "core_dump_elf.h"
const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_common";
#if CONFIG_ESP32_ENABLE_COREDUMP
#if CONFIG_ESP32_COREDUMP_DATA_FORMAT_BIN
static inline uint32_t esp_core_dump_get_tcb_len()
{
if (COREDUMP_TCB_SIZE % sizeof(uint32_t)) {
return ((COREDUMP_TCB_SIZE / sizeof(uint32_t) + 1) * sizeof(uint32_t));
}
return COREDUMP_TCB_SIZE;
}
static inline uint32_t esp_core_dump_get_stack_len(uint32_t stack_start, uint32_t stack_end)
{
uint32_t len = stack_end - stack_start;
// Take stack padding into account
return (len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1);
}
static esp_err_t esp_core_dump_save_task(core_dump_write_config_t *write_cfg,
core_dump_task_header_t *task)
{
esp_err_t err = ESP_FAIL;
uint32_t stk_vaddr, stk_len;
uint32_t stk_paddr = esp_core_dump_get_stack(task, &stk_vaddr, &stk_len);
stk_len = esp_core_dump_get_stack_len(stk_vaddr, stk_vaddr+stk_len);
// Save TCB address, stack base and stack top addr
err = write_cfg->write(write_cfg->priv, (void*)task, sizeof(core_dump_task_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write task header, error=%d!", err);
return err;
}
// Save TCB block
err = write_cfg->write(write_cfg->priv, task->tcb_addr, esp_core_dump_get_tcb_len());
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write TCB, error=%d!", err);
return err;
}
// Save task stack
err = write_cfg->write(write_cfg->priv, (void*)stk_paddr, stk_len);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write stack for task (TCB:%x), stack_start=%x, error=%d!",
task->tcb_addr,
stk_vaddr,
err);
return err;
}
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x) dump is saved.",
task->tcb_addr);
return ESP_OK;
}
static esp_err_t esp_core_dump_save_mem_segment(core_dump_write_config_t* write_cfg,
core_dump_mem_seg_header_t* seg)
{
esp_err_t err = ESP_FAIL;
if (!esp_core_dump_mem_seg_is_sane(seg->start, seg->size)) {
ESP_COREDUMP_LOGE("Failed to write memory segment, (%x, %lu)!",
seg->start, seg->size);
return ESP_FAIL;
}
// Save TCB address, stack base and stack top addr
err = write_cfg->write(write_cfg->priv, (void*)seg, sizeof(core_dump_mem_seg_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write memory segment header, error=%d!", err);
return err;
}
// Save memory contents
err = write_cfg->write(write_cfg->priv, (void*)seg->start, seg->size);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write memory segment, (%x, %lu), error=%d!",
seg->start, seg->size, err);
return err;
}
ESP_COREDUMP_LOG_PROCESS("Memory segment (%x, %lu) is saved.",
seg->start, seg->size);
return ESP_OK;
}
static esp_err_t esp_core_dump_write_binary(void *frame, core_dump_write_config_t *write_cfg)
{
esp_err_t err;
core_dump_task_header_t tasks[CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM];
uint32_t tcb_sz, task_num, tcb_sz_padded;
bool task_is_valid = false;
uint32_t data_len = 0, i;
union
{
core_dump_header_t hdr;
core_dump_task_header_t task_hdr;
} dump_data;
static core_dump_task_header_t tasks[CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM];
uint32_t task_num, tcb_sz = esp_core_dump_get_tcb_len();
uint32_t data_len = 0, task_id;
int curr_task_index = COREDUMP_CURR_TASK_NOT_FOUND;
core_dump_header_t hdr;
core_dump_mem_seg_header_t interrupted_task_stack;
task_num = esp_core_dump_get_tasks_snapshot(tasks, CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM, &tcb_sz);
ESP_COREDUMP_LOGI("Found tasks: (%d)!", task_num);
// Take TCB padding into account, actual TCB size will be stored in header
if (tcb_sz % sizeof(uint32_t))
tcb_sz_padded = (tcb_sz / sizeof(uint32_t) + 1) * sizeof(uint32_t);
else
tcb_sz_padded = tcb_sz;
task_num = esp_core_dump_get_tasks_snapshot(tasks, CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM);
ESP_COREDUMP_LOGI("Found tasks: %d!", task_num);
// Verifies all tasks in the snapshot
for (i = 0; i < task_num; i++) {
task_is_valid = esp_core_dump_process_tcb(frame, &tasks[i], tcb_sz);
// Check if task tcb is corrupted
if (!task_is_valid) {
write_cfg->bad_tasks_num++;
continue;
} else {
data_len += (tcb_sz_padded + sizeof(core_dump_task_header_t));
}
uint32_t len = 0;
task_is_valid = esp_core_dump_process_stack(&tasks[i], &len);
if (task_is_valid) {
// Increase core dump size by task stack size
data_len += len;
} else {
// If task tcb is ok but stack is corrupted
for (task_id = 0; task_id < task_num; task_id++) {
bool is_current_task = false, stack_is_valid = false;
bool tcb_is_valid = esp_core_dump_check_task(frame, &tasks[task_id], &is_current_task, &stack_is_valid);
// Check if task tcb or stack is corrupted
if (!tcb_is_valid || !stack_is_valid) {
// If tcb or stack for task is corrupted count task as broken
write_cfg->bad_tasks_num++;
}
if (is_current_task) {
curr_task_index = task_id; // save current crashed task index in the snapshot
ESP_COREDUMP_LOG_PROCESS("Task #%d (TCB:%x) is first crashed task.",
task_id,
tasks[task_id].tcb_addr);
}
// Increase core dump size by task stack size
uint32_t stk_vaddr, stk_len;
esp_core_dump_get_stack(&tasks[task_id], &stk_vaddr, &stk_len);
data_len += esp_core_dump_get_stack_len(stk_vaddr, stk_vaddr+stk_len);
// Add tcb size
data_len += (tcb_sz + sizeof(core_dump_task_header_t));
}
if (esp_core_dump_in_isr_context()) {
interrupted_task_stack.start = tasks[curr_task_index].stack_start;
interrupted_task_stack.size = esp_core_dump_get_stack_len(tasks[curr_task_index].stack_start, tasks[curr_task_index].stack_end);
// size of the task's stack has been already taken into account, also addresses have also been checked
data_len += sizeof(core_dump_mem_seg_header_t);
tasks[curr_task_index].stack_start = (uint32_t)frame;
tasks[curr_task_index].stack_end = esp_core_dump_get_isr_stack_end();
ESP_COREDUMP_LOG_PROCESS("Add ISR stack %lu to %lu", tasks[curr_task_index].stack_end - tasks[curr_task_index].stack_start, data_len);
// take into account size of the ISR stack
data_len += esp_core_dump_get_stack_len(tasks[curr_task_index].stack_start, tasks[curr_task_index].stack_end);
}
// Check if current task TCB is broken
if (curr_task_index == COREDUMP_CURR_TASK_NOT_FOUND) {
ESP_COREDUMP_LOG_PROCESS("The current crashed task is broken.");
curr_task_index = 0;
}
// Add core dump header size
data_len += sizeof(core_dump_header_t);
ESP_COREDUMP_LOG_PROCESS("Core dump len = %lu (%d %d)", data_len, task_num, write_cfg->bad_tasks_num);
ESP_COREDUMP_LOG_PROCESS("Core dump length=%lu, tasks processed: %d, broken tasks: %d",
data_len, task_num, write_cfg->bad_tasks_num);
// Prepare write
if (write_cfg->prepare) {
err = write_cfg->prepare(write_cfg->priv, &data_len);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to prepare core dump (%d)!", err);
ESP_COREDUMP_LOGE("Failed to prepare core dump, error=%d!", err);
return err;
}
}
// Write start
if (write_cfg->start) {
err = write_cfg->start(write_cfg->priv);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to start core dump (%d)!", err);
ESP_COREDUMP_LOGE("Failed to start core dump, error=%d!", err);
return err;
}
}
// Write header
dump_data.hdr.data_len = data_len;
dump_data.hdr.version = COREDUMP_VERSION;
dump_data.hdr.tasks_num = task_num - write_cfg->bad_tasks_num;
dump_data.hdr.tcb_sz = tcb_sz;
err = write_cfg->write(write_cfg->priv, &dump_data, sizeof(core_dump_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write core dump header (%d)!", err);
return err;
}
// Write tasks
for (i = 0; i < task_num; i++) {
if (!esp_tcb_addr_is_sane((uint32_t)tasks[i].tcb_addr, tcb_sz)) {
ESP_COREDUMP_LOG_PROCESS("Skip TCB with bad addr %x!", tasks[i].tcb_addr);
continue;
}
ESP_COREDUMP_LOG_PROCESS("Dump task %x", tasks[i].tcb_addr);
// Save TCB address, stack base and stack top addr
dump_data.task_hdr.tcb_addr = tasks[i].tcb_addr;
dump_data.task_hdr.stack_start = tasks[i].stack_start;
dump_data.task_hdr.stack_end = tasks[i].stack_end;
err = write_cfg->write(write_cfg->priv, (void*)&dump_data, sizeof(core_dump_task_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write task header (%d)!", err);
return err;
}
// Save TCB
err = write_cfg->write(write_cfg->priv, tasks[i].tcb_addr, tcb_sz);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write TCB (%d)!", err);
return err;
}
// Save task stack
if (tasks[i].stack_start != 0 && tasks[i].stack_end != 0) {
err = write_cfg->write(write_cfg->priv, (void*)tasks[i].stack_start,
tasks[i].stack_end - tasks[i].stack_start);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write task stack (%d)!", err);
return err;
}
} else {
ESP_COREDUMP_LOG_PROCESS("Skip corrupted task %x stack!", tasks[i].tcb_addr);
}
}
// write end
// Write header
hdr.data_len = data_len;
hdr.version = COREDUMP_VERSION;
hdr.tasks_num = task_num; // save all the tasks in snapshot even broken
hdr.mem_segs_num = 0;
if (xPortInterruptedFromISRContext()) {
hdr.mem_segs_num++; // stack of interrupted task
}
hdr.tcb_sz = tcb_sz;
err = write_cfg->write(write_cfg->priv, &hdr, sizeof(core_dump_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write core dump header error=%d!", err);
return err;
}
// Write first crashed task data first (not always first task in the snapshot)
err = esp_core_dump_save_task(write_cfg, &tasks[curr_task_index]);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to save first crashed task #%d (TCB:%x), error=%d!",
curr_task_index, tasks[curr_task_index].tcb_addr, err);
return err;
}
// Write all other tasks in the snapshot
for (task_id = 0; task_id < task_num; task_id++) {
// Skip first crashed task
if (task_id == curr_task_index) {
continue;
}
err = esp_core_dump_save_task(write_cfg, &tasks[task_id]);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to save core dump task #%d (TCB:%x), error=%d!",
task_id, tasks[curr_task_index].tcb_addr, err);
return err;
}
}
if (xPortInterruptedFromISRContext()) {
err = esp_core_dump_save_mem_segment(write_cfg, &interrupted_task_stack);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to save interrupted task stack, error=%d!", err);
return err;
}
}
// Write end
if (write_cfg->end) {
err = write_cfg->end(write_cfg->priv);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to end core dump (%d)!", err);
ESP_COREDUMP_LOGE("Failed to end core dump error=%d!", err);
return err;
}
}
if (write_cfg->bad_tasks_num) {
ESP_COREDUMP_LOGE("Skipped %d tasks with bad TCB!", write_cfg->bad_tasks_num);
ESP_COREDUMP_LOGE("Found %d broken tasks!", write_cfg->bad_tasks_num);
}
return err;
}
#endif
inline void esp_core_dump_write(void *frame, core_dump_write_config_t *write_cfg)
{
esp_err_t err = esp_core_dump_write_binary(frame, write_cfg);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Core dump write binary failed with error: %d", err);
}
}
esp_core_dump_setup_stack();
#ifndef CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE
esp_err_t err = ESP_ERR_NOT_SUPPORTED;
#if CONFIG_ESP32_COREDUMP_DATA_FORMAT_BIN
err = esp_core_dump_write_binary(frame, write_cfg);
#elif CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF
err = esp_core_dump_write_elf(frame, write_cfg);
#endif
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Core dump write binary failed with error=%d", err);
}
#endif
esp_core_dump_report_stack_usage();
}
void esp_core_dump_init(void)
{
@ -173,14 +276,16 @@ esp_err_t esp_core_dump_image_get(size_t* out_addr, size_t *out_size)
return ESP_ERR_INVALID_ARG;
}
const esp_partition_t *core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_COREDUMP, NULL);
const esp_partition_t *core_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
ESP_PARTITION_SUBTYPE_DATA_COREDUMP,
NULL);
if (!core_part) {
ESP_LOGE(TAG, "No core dump partition found!");
return ESP_FAIL;
return ESP_ERR_NOT_FOUND;
}
if (core_part->size < sizeof(uint32_t)) {
ESP_LOGE(TAG, "Too small core dump partition!");
return ESP_FAIL;
return ESP_ERR_INVALID_SIZE;
}
err = esp_partition_mmap(core_part, 0, sizeof(uint32_t),
@ -189,9 +294,14 @@ esp_err_t esp_core_dump_image_get(size_t* out_addr, size_t *out_size)
ESP_LOGE(TAG, "Failed to mmap core dump data (%d)!", err);
return err;
}
uint32_t *dw = (uint32_t *)core_data;
*out_size = *dw;
spi_flash_munmap(core_data_handle);
if ((*out_size < sizeof(uint32_t)) || (*out_size > core_part->size)) {
ESP_LOGE(TAG, "Incorrect size of core dump image: %d", *out_size);
return ESP_ERR_INVALID_SIZE;
}
// remap full core dump with CRC
err = esp_partition_mmap(core_part, 0, *out_size,
@ -200,16 +310,38 @@ esp_err_t esp_core_dump_image_get(size_t* out_addr, size_t *out_size)
ESP_LOGE(TAG, "Failed to mmap core dump data (%d)!", err);
return err;
}
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
uint32_t *crc = (uint32_t *)(((uint8_t *)core_data) + *out_size);
crc--; // Point to CRC field
// Calc CRC over core dump data except for CRC field
// Calculate CRC over core dump data except for CRC field
core_dump_crc_t cur_crc = crc32_le(0, (uint8_t const *)core_data, *out_size - sizeof(core_dump_crc_t));
if (*crc != cur_crc) {
ESP_LOGD(TAG, "Core dump CRC offset 0x%x, data size: %u",
(uint32_t)((uint32_t)crc - (uint32_t)core_data), *out_size);
ESP_LOGE(TAG, "Core dump data CRC check failed: 0x%x -> 0x%x!", *crc, cur_crc);
spi_flash_munmap(core_data_handle);
return ESP_FAIL;
return ESP_ERR_INVALID_CRC;
}
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
uint8_t* sha256_ptr = (uint8_t*)(((uint8_t *)core_data) + *out_size);
sha256_ptr -= COREDUMP_SHA256_LEN;
ESP_LOGD(TAG, "Core dump data offset, size: %d, %u!",
(uint32_t)((uint32_t)sha256_ptr - (uint32_t)core_data), *out_size);
unsigned char sha_output[COREDUMP_SHA256_LEN];
mbedtls_sha256_context ctx;
ESP_LOGI(TAG, "Calculate SHA256 for coredump:");
(void)esp_core_dump_sha(&ctx, core_data, *out_size - COREDUMP_SHA256_LEN, sha_output);
if (memcmp((uint8_t*)sha256_ptr, (uint8_t*)sha_output, COREDUMP_SHA256_LEN) != 0) {
ESP_LOGE(TAG, "Core dump data SHA256 check failed:");
esp_core_dump_print_sha256("Calculated SHA256", (uint8_t*)sha_output);
esp_core_dump_print_sha256("Image SHA256",(uint8_t*)sha256_ptr);
spi_flash_munmap(core_data_handle);
return ESP_ERR_INVALID_CRC;
} else {
ESP_LOGI(TAG, "Core dump data SHA256 is correct");
}
#endif
spi_flash_munmap(core_data_handle);
*out_addr = core_part->address;

View file

@ -0,0 +1,670 @@
// Copyright 2015-2019 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.
#include <string.h>
#include "esp_attr.h"
#include "esp_partition.h"
#include "esp_ota_ops.h"
#include "sdkconfig.h"
#include "core_dump_elf.h"
#define ELF_CLASS ELFCLASS32
#include "elf.h" // for ELF file types
#define ELF_SEG_HEADERS_COUNT(_self_, _task_num_) (uint32_t)((_task_num_) * 2/*stack + tcb*/ \
+ 1/* regs notes */ + 1/* ver info + extra note */ + ((_self_)->interrupted_task.stack_start ? 1 : 0))
#define ELF_HLEN 52
#define ELF_CORE_SEC_TYPE 1
#define ELF_PR_STATUS_SEG_NUM 0
#define ELF_ESP_CORE_DUMP_INFO_TYPE 8266
#define ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE 677
#define ELF_NOTE_NAME_MAX_SIZE 32
#define ELF_APP_SHA256_SIZE 66
#define ELF_CHECK_ERR(a, ret_val, str, ...) \
if (!(a)) { \
ESP_COREDUMP_LOGE("%s(%u): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
return (ret_val); \
}
typedef enum
{
ELF_STAGE_CALC_SPACE = 0,
ELF_STAGE_PLACE_HEADERS = 1,
ELF_STAGE_PLACE_DATA = 2
} core_dump_elf_stages_t;
typedef enum _elf_err_t
{
ELF_PROC_ERR_SKIP_HEADER = 0,
ELF_PROC_ERR_STACK_CORRUPTED = -1,
ELF_PROC_ERR_WRITE_FAIL = -2,
ELF_PROC_ERR_OTHER = -3
} core_dump_elf_proc_err_t;
typedef struct _core_dump_task_info_t
{
elf_phdr* phdr;
void* frame;
core_dump_task_header_t* task_hdr;
uint32_t task_id;
size_t tcb_sz;
int* size_ptr;
} core_dump_task_data_t;
typedef struct
{
uint32_t version; // coredump version
uint8_t app_elf_sha256[ELF_APP_SHA256_SIZE]; // sha256 of elf file
} core_dump_elf_version_info_t;
const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_elf";
// Main ELF handle type
typedef struct _core_dump_elf_t
{
core_dump_elf_version_info_t elf_version_info;
uint16_t elf_stage;
uint32_t elf_next_data_offset;
uint32_t bad_tasks_num;
core_dump_task_header_t interrupted_task;
core_dump_write_config_t * write_cfg;
} core_dump_elf_t;
// Represents lightweight implementation to save core dump data into ELF formatted binary
#define ALIGN(b, var) var = align(b, var)
#if CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF
static inline uint32_t align(uint32_t width, uint32_t in)
{
return (in + (width - 1)) & -width;
}
// Builds elf header and check all data offsets
static int elf_write_file_header(core_dump_elf_t *self, uint32_t seg_count)
{
elfhdr elf_hdr; // declare as static to save stack space
if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) {
ESP_COREDUMP_LOG_PROCESS("Segment count %u", seg_count);
memset(&elf_hdr, 0, sizeof(elfhdr));
elf_hdr.e_ident[0] = ELFMAG0;
elf_hdr.e_ident[1] = ELFMAG1;
elf_hdr.e_ident[2] = ELFMAG2;
elf_hdr.e_ident[3] = ELFMAG3;
elf_hdr.e_ident[4] = ELFCLASS32;
elf_hdr.e_ident[5] = ELFDATA2LSB;
elf_hdr.e_ident[6] = EV_CURRENT;
elf_hdr.e_ident[7] = ELFOSABI_NONE;
elf_hdr.e_ident[8] = 0;
elf_hdr.e_type = ET_CORE;
elf_hdr.e_machine = esp_core_dump_get_arch_id();
elf_hdr.e_flags = 0;
elf_hdr.e_version = EV_CURRENT;
elf_hdr.e_entry = 0;
_Static_assert(sizeof(elfhdr) == ELF_HLEN, "Invalid ELF header struct length!");
elf_hdr.e_phoff = sizeof(elfhdr); // program header table's file offset in bytes.
elf_hdr.e_phentsize = sizeof(elf_phdr); // size in bytes of one entry in the file program header table
elf_hdr.e_phnum = seg_count; // number of program segments
elf_hdr.e_shoff = 0; // section header table's file offset in bytes.
elf_hdr.e_ehsize = sizeof(elfhdr); // elf header size
elf_hdr.e_shentsize = sizeof(elf_shdr); // section header's size in bytes.
elf_hdr.e_shnum = 0; // initial section counter is 0
elf_hdr.e_shstrndx = SHN_UNDEF; // do not use string table
// write built elf header into elf image
esp_err_t err = self->write_cfg->write(self->write_cfg->priv, (void*)&elf_hdr, sizeof(elf_hdr));
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF header failure (%d)", err);
ESP_COREDUMP_LOG_PROCESS("Add file header %u bytes", sizeof(elf_hdr));
}
return self->elf_stage == ELF_STAGE_PLACE_DATA ? 0 : sizeof(elf_hdr);
}
static int elf_write_segment_header(core_dump_elf_t *self, elf_phdr* phdr)
{
ELF_CHECK_ERR(phdr, ELF_PROC_ERR_SKIP_HEADER,
"Header is skipped, stage=(%d).", self->elf_stage);
phdr->p_offset = self->elf_next_data_offset;
// set segment data information and write it into image
esp_err_t err = self->write_cfg->write(self->write_cfg->priv, (void*)phdr, sizeof(elf_phdr));
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF segment header failure (%d)", err);
ESP_COREDUMP_LOG_PROCESS("Add segment header %u bytes: type %d, sz %u, off = 0x%x",
sizeof(elf_phdr), phdr->p_type, phdr->p_filesz, phdr->p_offset);
return sizeof(elf_phdr);
}
static int elf_add_segment(core_dump_elf_t *self,
uint32_t type, uint32_t vaddr,
void* data, uint32_t data_sz)
{
esp_err_t err = ESP_FAIL;
elf_phdr seg_hdr = { 0 };
int data_len = data_sz;
ELF_CHECK_ERR((data != NULL), ELF_PROC_ERR_OTHER,
"Invalid data for segment.");
ALIGN(4, data_len);
if (self->elf_stage == ELF_STAGE_CALC_SPACE) {
return data_len + sizeof(elf_phdr);
}
if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) {
seg_hdr.p_type = type;
seg_hdr.p_vaddr = vaddr;
seg_hdr.p_paddr = vaddr;
seg_hdr.p_filesz = data_len;
seg_hdr.p_memsz = data_len;
seg_hdr.p_flags = (PF_R | PF_W);
int ret = elf_write_segment_header(self, &seg_hdr);
ELF_CHECK_ERR((ret > 0), ret,
"Write ELF segment data failure (%d)", ret);
self->elf_next_data_offset += data_len;
return ret;
}
ESP_COREDUMP_LOG_PROCESS("Add segment size=%u, start_off=0x%x",
(uint32_t)data_len, self->elf_next_data_offset);
// write segment data only when write function is set and phdr = NULL
// write data into segment
err = self->write_cfg->write(self->write_cfg->priv, data, (uint32_t)data_len);
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF segment data failure (%d)", err);
self->elf_next_data_offset += data_len;
return data_len;
}
static int elf_write_note(core_dump_elf_t *self,
const char* name,
uint32_t type,
void* data,
uint32_t data_sz)
{
esp_err_t err = ESP_FAIL;
// temporary buffer for note name
static char name_buffer[ELF_NOTE_NAME_MAX_SIZE] = { 0 };
elf_note note_hdr = { 0 };
uint32_t name_len = strlen(name) + 1; // get name length including terminator
uint32_t data_len = data_sz;
ELF_CHECK_ERR(data, ELF_PROC_ERR_OTHER,
"Invalid data pointer %x.", (uint32_t)data);
ELF_CHECK_ERR((name_len <= ELF_NOTE_NAME_MAX_SIZE), 0,
"Segment note name is too long %d.", name_len);
ALIGN(4, data_len);
ALIGN(4, name_len);
uint32_t note_size = name_len + data_len + sizeof(elf_note);
ALIGN(4, note_size);
// write segment data during second pass
if (self->elf_stage == ELF_STAGE_PLACE_DATA) {
memcpy((void*)name_buffer, (void*)name, name_len);
note_hdr.n_namesz = name_len;
note_hdr.n_descsz = data_sz;
note_hdr.n_type = type;
// write note header
err = self->write_cfg->write(self->write_cfg->priv, (void*)&note_hdr, sizeof(note_hdr));
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF note header failure (%d)", err);
// write note name
err = self->write_cfg->write(self->write_cfg->priv, (void*)name_buffer, name_len);
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF note name failure (%d)", err);
// write note data
err = self->write_cfg->write(self->write_cfg->priv, (void*)data, data_len);
ELF_CHECK_ERR((err == ESP_OK), ELF_PROC_ERR_WRITE_FAIL,
"Write ELF note data failure (%d)", err);
ESP_COREDUMP_LOG_PROCESS("Add note size=%d, start_off=0x%x",
note_size, self->elf_next_data_offset);
}
return note_size; // return actual note size
}
static int elf_add_note(core_dump_elf_t *self,
const char* name,
uint32_t type,
void* data,
uint32_t data_sz)
{
ELF_CHECK_ERR((data != NULL), ELF_PROC_ERR_OTHER,
"Invalid data pointer for segment");
int note_size = elf_write_note(self, name, type, data, data_sz);
ELF_CHECK_ERR((note_size > 0), note_size,
"Write ELF note data failure, returned (%d)", note_size);
return note_size; // return actual note segment size
}
// Append note with registers dump to segment note
static int elf_add_regs(core_dump_elf_t *self, core_dump_task_header_t *task)
{
void *reg_dump;
uint32_t len = esp_core_dump_get_task_regs_dump(task, &reg_dump);
if (len == 0) {
ESP_COREDUMP_LOGE("Zero size register dump for task 0x%x!", task->tcb_addr);
return ELF_PROC_ERR_OTHER;
}
// append note data with dump to existing note
return elf_add_note(self,
"CORE", // note name
ELF_CORE_SEC_TYPE, // note type for reg dump
reg_dump, // register dump with pr_status
len);
}
static int elf_add_stack(core_dump_elf_t *self, core_dump_task_header_t *task)
{
uint32_t stack_vaddr, stack_len = 0, stack_paddr = 0;
ELF_CHECK_ERR((task), ELF_PROC_ERR_OTHER, "Invalid task pointer.");
stack_paddr = esp_core_dump_get_stack(task, &stack_vaddr, &stack_len);
ESP_COREDUMP_LOG_PROCESS("Add stack for task 0x%x: addr 0x%x, sz %u",
task->tcb_addr, stack_vaddr, stack_len);
int ret = elf_add_segment(self, PT_LOAD,
(uint32_t)stack_vaddr,
(void*)stack_paddr,
(uint32_t) stack_len);
return ret;
}
static int elf_add_tcb(core_dump_elf_t *self, core_dump_task_header_t *task)
{
ELF_CHECK_ERR((task), ELF_PROC_ERR_OTHER, "Invalid task pointer.");
// add task tcb data into program segment of ELF
ESP_COREDUMP_LOG_PROCESS("Add TCB for task 0x%x: addr 0x%x, sz %u",
task->tcb_addr, task->tcb_addr, COREDUMP_TCB_SIZE);
int ret = elf_add_segment(self, PT_LOAD,
(uint32_t)task->tcb_addr,
(void*)task->tcb_addr,
COREDUMP_TCB_SIZE);
return ret;
}
// get index of current crashed task (not always first task in the snapshot)
static int elf_get_current_task_index(core_dump_task_header_t *tasks,
uint32_t task_num)
{
int task_id;
int curr_task_index = COREDUMP_CURR_TASK_NOT_FOUND;
void* curr_task_handle = esp_core_dump_get_current_task_handle();
// get index of current crashed task (not always first task in the snapshot)
for (task_id = 0; task_id < task_num; task_id++) {
bool tcb_is_valid = esp_core_dump_tcb_addr_is_sane((uint32_t)tasks[task_id].tcb_addr);
bool stack_is_valid = esp_core_dump_check_stack(tasks[task_id].stack_start, tasks[task_id].stack_end);
if (stack_is_valid && tcb_is_valid && curr_task_handle == tasks[task_id].tcb_addr) {
curr_task_index = task_id; // save current crashed task index in the snapshot
ESP_COREDUMP_LOG_PROCESS("Task #%d, (TCB:%x) is current crashed task.",
task_id,
tasks[task_id].tcb_addr);
}
}
return curr_task_index;
}
static int elf_process_task_regdump(core_dump_elf_t *self, void *frame, core_dump_task_header_t *task)
{
bool task_is_valid = false;
bool task_is_current = false;
ELF_CHECK_ERR((task), ELF_PROC_ERR_OTHER, "Invalid input data.");
if (self->elf_stage == ELF_STAGE_CALC_SPACE) {
// Check if task tcb is corrupted (do not update the header, save as is)
task_is_valid = esp_core_dump_check_task(frame, task, &task_is_current, NULL);
if (!task_is_valid) {
if (task_is_current) {
ESP_COREDUMP_LOG_PROCESS("Task has incorrect (TCB:%x)!",
task->tcb_addr);
} else {
ESP_COREDUMP_LOG_PROCESS("The current crashed task has broken (TCB:%x)!",
task->tcb_addr);
}
self->bad_tasks_num++;
}
}
// extract registers from stack and apply elf data size for stack section
return elf_add_regs(self, task);
}
static int elf_process_task_tcb(core_dump_elf_t *self, core_dump_task_header_t *task)
{
int ret = ELF_PROC_ERR_OTHER;
ELF_CHECK_ERR((task), ELF_PROC_ERR_OTHER, "Invalid input data.");
// save tcb of the task as is and apply segment size
ret = elf_add_tcb(self, task);
if (ret > 0) {
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x) processing completed.",
task->tcb_addr);
} else {
ESP_COREDUMP_LOGE("Task (TCB:%x) processing failure = %d",
task->tcb_addr,
ret);
}
return ret;
}
static int elf_process_task_stack(core_dump_elf_t *self, core_dump_task_header_t *task)
{
int ret = ELF_PROC_ERR_OTHER;
ELF_CHECK_ERR((task), ELF_PROC_ERR_OTHER, "Invalid input data.");
ret = elf_add_stack(self, task);
if (ret > 0) {
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x), (Stack:%x) stack is processed.",
task->tcb_addr,
task->stack_start);
} else {
ESP_COREDUMP_LOGE("Task (TCB:%x), (Stack:%x), stack processing failure = %d.",
task->tcb_addr,
task->stack_start,
ret);
}
return ret;
}
static int elf_process_note_segment(core_dump_elf_t *self, int notes_size)
{
int ret;
elf_phdr seg_hdr = { 0 };
if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) {
// segment header for PR_STATUS notes
seg_hdr.p_type = PT_NOTE;
seg_hdr.p_vaddr = 0;
seg_hdr.p_paddr = 0;
seg_hdr.p_filesz = notes_size;
seg_hdr.p_memsz = notes_size;
seg_hdr.p_flags = (PF_R | PF_W);
ret = elf_write_segment_header(self, &seg_hdr);
ELF_CHECK_ERR((ret > 0), ret, "NOTE segment header write failure, returned (%d).", ret);
self->elf_next_data_offset += notes_size;
return sizeof(seg_hdr);
} else if (self->elf_stage == ELF_STAGE_CALC_SPACE) {
notes_size += sizeof(seg_hdr);
} else {
// in "Place Data" phase segment body is been already filled by other functions
ESP_COREDUMP_LOG_PROCESS("Add NOTE segment, size=%d, start_off=0x%x",
notes_size, self->elf_next_data_offset);
self->elf_next_data_offset += notes_size;
}
return (int)notes_size;
}
static int elf_process_tasks_regs(core_dump_elf_t *self, void* frame,
core_dump_task_header_t* tasks,
uint32_t task_num)
{
int len = 0;
uint32_t curr_task_index = elf_get_current_task_index(tasks, task_num);
if (curr_task_index == COREDUMP_CURR_TASK_NOT_FOUND) {
ESP_COREDUMP_LOG_PROCESS("The current crashed task is broken.");
curr_task_index = 0;
}
// place current task dump first
int ret = elf_process_task_regdump(self, frame, &tasks[curr_task_index]);
if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) {
// when writing segments headers this function writes nothing
ELF_CHECK_ERR((ret >= 0), ret, "Task #%d, PR_STATUS write failed, return (%d).", curr_task_index, ret);
} else {
ELF_CHECK_ERR((ret > 0), ret, "Task #%d, PR_STATUS write failed, return (%d).", curr_task_index, ret);
}
len += ret;
// processes PR_STATUS and register dump for each task
// each call to the processing function appends PR_STATUS note into note segment
// and writes data or updates the segment note header accordingly (if phdr is set)
for (int task_id = 0; task_id < task_num; task_id++) {
if (task_id == curr_task_index) {
continue; // skip current task (already processed)
}
ret = elf_process_task_regdump(self, frame, &tasks[task_id]);
if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) {
// when writing segments headers this function writes nothing
ELF_CHECK_ERR((ret >= 0), ret, "Task #%d, PR_STATUS write failed, return (%d).", task_id, ret);
} else {
ELF_CHECK_ERR((ret > 0), ret, "Task #%d, PR_STATUS write failed, return (%d).", task_id, ret);
}
len += ret;
}
ret = elf_process_note_segment(self, len);
ELF_CHECK_ERR((ret > 0), ret,
"PR_STATUS note segment processing failure, returned(%d).", ret);
if (esp_core_dump_in_isr_context()) {
if (self->elf_stage == ELF_STAGE_CALC_SPACE) {
// in this stage we can safely replace task's stack with IRQ's one
// if task had corrupted stack it was replaced with fake one in HW dependent code called by elf_process_task_regdump()
// in the "write data" stage registers from ISR's stack will be saved in PR_STATUS
self->interrupted_task.stack_start = tasks[curr_task_index].stack_start;
self->interrupted_task.stack_end = tasks[curr_task_index].stack_end;
uint32_t isr_stk_end = esp_core_dump_get_isr_stack_end();
ESP_COREDUMP_LOG_PROCESS("Add ISR stack %lu (%x - %x)", isr_stk_end - (uint32_t)frame, (uint32_t)frame, isr_stk_end);
tasks[curr_task_index].stack_start = (uint32_t)frame;
tasks[curr_task_index].stack_end = isr_stk_end;
}
// actually we write current task's stack here which was replaced by ISR's
len = elf_add_stack(self, &self->interrupted_task);
ELF_CHECK_ERR((len > 0), len, "Interrupted task stack write failed, return (%d).", len);
ret += len;
}
return ret;
}
static int elf_write_tasks_data(core_dump_elf_t *self, void* frame,
core_dump_task_header_t* tasks,
uint32_t task_num)
{
int elf_len = 0;
int task_id;
int ret = ELF_PROC_ERR_OTHER;
ELF_CHECK_ERR((frame && tasks), ELF_PROC_ERR_OTHER, "Invalid input data.");
ret = elf_process_tasks_regs(self, frame, tasks, task_num);
ELF_CHECK_ERR((ret > 0), ret, "Tasks regs addition failed, return (%d).", ret);
elf_len += ret;
self->bad_tasks_num = 0; // reset bad task counter
// processes all task's stack data and writes segment data into partition
// if flash configuration is set
for (task_id = 0; task_id < task_num; task_id++) {
ret = elf_process_task_tcb(self, &tasks[task_id]);
ELF_CHECK_ERR((ret > 0), ret,
"Task #%d, TCB write failed, return (%d).", task_id, ret);
elf_len += ret;
ret = elf_process_task_stack(self, &tasks[task_id]);
ELF_CHECK_ERR((ret != ELF_PROC_ERR_WRITE_FAIL), ELF_PROC_ERR_WRITE_FAIL,
"Task #%d, stack write failed, return (%d).", task_id, ret);
elf_len += ret;
}
return elf_len;
}
static int elf_write_core_dump_info(core_dump_elf_t *self)
{
void *extra_info;
int data_len = (int)sizeof(self->elf_version_info.app_elf_sha256);
data_len = esp_ota_get_app_elf_sha256((char*)self->elf_version_info.app_elf_sha256, (size_t)data_len);
ESP_COREDUMP_LOG_PROCESS("Application SHA256='%s', length=%d.",
self->elf_version_info.app_elf_sha256, data_len);
self->elf_version_info.version = COREDUMP_VERSION;
int ret = elf_add_note(self,
"ESP_CORE_DUMP_INFO",
ELF_ESP_CORE_DUMP_INFO_TYPE,
&self->elf_version_info,
sizeof(self->elf_version_info));
ELF_CHECK_ERR((ret > 0), ret, "Version info note write failed. Returned (%d).", ret);
data_len = ret;
uint32_t extra_info_len = esp_core_dump_get_extra_info(&extra_info);
if (extra_info_len == 0) {
ESP_COREDUMP_LOGE("Zero size extra info!");
return ELF_PROC_ERR_OTHER;
}
ret = elf_add_note(self,
"EXTRA_INFO",
ELF_ESP_CORE_DUMP_EXTRA_INFO_TYPE,
extra_info,
extra_info_len);
ELF_CHECK_ERR((ret > 0), ret, "Extra info note write failed. Returned (%d).", ret);
data_len += ret;
ret = elf_process_note_segment(self, data_len);
ELF_CHECK_ERR((ret > 0), ret,
"EXTRA_INFO note segment processing failure, returned(%d).", ret);
return ret;
}
static int esp_core_dump_do_write_elf_pass(core_dump_elf_t *self, void* frame,
core_dump_task_header_t* tasks,
uint32_t task_num)
{
int tot_len = 0;
int data_sz = elf_write_file_header(self, ELF_SEG_HEADERS_COUNT(self, task_num));
if (self->elf_stage == ELF_STAGE_PLACE_DATA) {
ELF_CHECK_ERR((data_sz >= 0), data_sz, "ELF header writing error, returned (%d).", data_sz);
} else {
ELF_CHECK_ERR((data_sz > 0), data_sz, "ELF header writing error, returned (%d).", data_sz);
}
tot_len += data_sz;
// Calculate whole size include headers for all tasks and main elf header
data_sz = elf_write_tasks_data(self, frame, tasks, task_num);
ELF_CHECK_ERR((data_sz > 0), data_sz, "ELF Size writing error, returned (%d).", data_sz);
tot_len += data_sz;
// write data with version control information and some extra info
// this should go after tasks processing
data_sz = elf_write_core_dump_info(self);
ELF_CHECK_ERR((data_sz > 0), data_sz, "Version info writing failed. Returned (%d).", data_sz);
tot_len += data_sz;
return tot_len;
}
esp_err_t esp_core_dump_write_elf(void *frame, core_dump_write_config_t *write_cfg)
{
esp_err_t err = ESP_OK;
static core_dump_task_header_t tasks[CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM];
static core_dump_elf_t self;
core_dump_header_t dump_hdr;
uint32_t tcb_sz = COREDUMP_TCB_SIZE, task_num;
int tot_len = sizeof(dump_hdr);
int write_len = sizeof(dump_hdr);
ELF_CHECK_ERR((frame && write_cfg), ESP_ERR_INVALID_ARG, "Invalid input data.");
task_num = esp_core_dump_get_tasks_snapshot(tasks, CONFIG_ESP32_CORE_DUMP_MAX_TASKS_NUM);
ESP_COREDUMP_LOGI("Found tasks: %d", task_num);
self.write_cfg = write_cfg;
esp_core_dump_init_extra_info();
// On first pass (do not write actual data), but calculate data length needed to allocate memory
self.elf_stage = ELF_STAGE_CALC_SPACE;
ESP_COREDUMP_LOG_PROCESS("================= Calc data size ===============");
int ret = esp_core_dump_do_write_elf_pass(&self, frame, tasks, task_num);
if (ret < 0) return ret;
tot_len += ret;
ESP_COREDUMP_LOG_PROCESS("Core dump tot_len=%lu, tasks processed: %d, broken tasks: %d",
tot_len, task_num, self.bad_tasks_num);
ESP_COREDUMP_LOG_PROCESS("============== Data size = %d bytes ============", tot_len);
// Prepare write elf
if (write_cfg->prepare) {
err = write_cfg->prepare(write_cfg->priv, (uint32_t*)&tot_len);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to prepare core dump storage (%d)!", err);
return err;
}
}
// Write start
if (write_cfg->start) {
err = write_cfg->start(write_cfg->priv);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to start core dump (%d)!", err);
return err;
}
}
write_cfg->bad_tasks_num = self.bad_tasks_num;
// Write core dump header
ALIGN(4, tot_len);
ALIGN(4, tcb_sz);
dump_hdr.data_len = tot_len;
dump_hdr.version = COREDUMP_VERSION;
dump_hdr.tasks_num = task_num; // broken tasks are repaired
dump_hdr.tcb_sz = tcb_sz;
dump_hdr.mem_segs_num = 0;
err = write_cfg->write(write_cfg->priv,
(void*)&dump_hdr,
sizeof(core_dump_header_t));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write core dump header (%d)!", err);
return err;
}
self.elf_stage = ELF_STAGE_PLACE_HEADERS;
// set initial offset to elf segments data area
self.elf_next_data_offset = sizeof(elfhdr) + ELF_SEG_HEADERS_COUNT(&self, task_num) * sizeof(elf_phdr);
ret = esp_core_dump_do_write_elf_pass(&self, frame, tasks, task_num);
if (ret < 0) return ret;
write_len += ret;
ESP_COREDUMP_LOG_PROCESS("============== Headers size = %d bytes ============", write_len);
self.elf_stage = ELF_STAGE_PLACE_DATA;
// set initial offset to elf segments data area, this is not necessary in this stage, just for pretty debug output
self.elf_next_data_offset = sizeof(elfhdr) + ELF_SEG_HEADERS_COUNT(&self, task_num) * sizeof(elf_phdr);
ret = esp_core_dump_do_write_elf_pass(&self, frame, tasks, task_num);
if (ret < 0) return ret;
write_len += ret;
ESP_COREDUMP_LOG_PROCESS("=========== Data written size = %d bytes ==========", write_len);
// Get checksum size
write_len += esp_core_dump_checksum_finish(write_cfg->priv, NULL);
if (write_len != tot_len) {
ESP_COREDUMP_LOGD("Write ELF failed (wrong length): %d != %d.", tot_len, write_len);
}
// Write end, update checksum
if (write_cfg->end) {
err = write_cfg->end(write_cfg->priv);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to end core dump (%d)!", err);
return err;
}
}
return err;
}
#endif //CONFIG_ESP32_COREDUMP_DATA_FORMAT_ELF

View file

@ -12,23 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "esp32/rom/crc.h"
#include "esp_partition.h"
#include "esp32/rom/crc.h"
#include "esp_core_dump_priv.h"
#include "esp_flash_internal.h"
#include "sdkconfig.h"
const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_flash";
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
typedef struct _core_dump_write_flash_data_t
{
uint32_t off; // current offset in partition
core_dump_crc_t crc; // CRC of dumped data
} core_dump_write_flash_data_t;
typedef struct _core_dump_partition_t
{
// core dump partition start
@ -48,6 +40,17 @@ typedef struct _core_dump_flash_config_t
// core dump flash data
static core_dump_flash_config_t s_core_flash_config;
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
#define ESP_COREDUMP_FLASH_WRITE(_off_, _data_, _len_) spi_flash_write(_off_, _data_, _len_)
#define ESP_COREDUMP_FLASH_READ(_off_, _data_, _len_) spi_flash_read(_off_, _data_, _len_)
#define ESP_COREDUMP_FLASH_ERASE(_off_, _len_) spi_flash_erase_range(_off_, _len_)
#else
#define ESP_COREDUMP_FLASH_WRITE(_off_, _data_, _len_) esp_flash_write(esp_flash_default_chip, _data_, _off_, _len_)
#define ESP_COREDUMP_FLASH_READ(_off_, _data_, _len_) esp_flash_read(esp_flash_default_chip, _data_, _off_, _len_)
#define ESP_COREDUMP_FLASH_ERASE(_off_, _len_) esp_flash_erase_region(esp_flash_default_chip, _off_, _len_)
#endif
static inline core_dump_crc_t esp_core_dump_calc_flash_config_crc(void)
{
return crc32_le(0, (uint8_t const *)&s_core_flash_config.partition, sizeof(s_core_flash_config.partition));
@ -69,80 +72,87 @@ void esp_core_dump_flash_init(void)
s_core_flash_config.partition_config_crc = esp_core_dump_calc_flash_config_crc();
}
static uint32_t esp_core_dump_write_flash_padded(size_t off, uint8_t *data, uint32_t data_size)
static esp_err_t esp_core_dump_flash_write_data(void *priv, uint8_t *data, uint32_t data_size)
{
esp_err_t err;
uint32_t data_len = 0, k, len;
union
{
uint8_t data8[4];
uint32_t data32;
} rom_data;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
uint32_t written = 0, wr_sz;
data_len = (data_size / sizeof(uint32_t)) * sizeof(uint32_t);
assert((wr_data->off + data_size) < s_core_flash_config.partition.size);
assert(off >= s_core_flash_config.partition.start);
assert((off + data_len + (data_size % sizeof(uint32_t) ? sizeof(uint32_t) : 0)) <=
s_core_flash_config.partition.start + s_core_flash_config.partition.size);
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
err = spi_flash_write(off, data, data_len);
#else
err = esp_flash_write(esp_flash_default_chip, data, off, data_len);
#endif
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err);
return 0;
if (wr_data->cached_bytes) {
if ((sizeof(wr_data->cached_data)-wr_data->cached_bytes) > data_size)
wr_sz = data_size;
else
wr_sz = sizeof(wr_data->cached_data)-wr_data->cached_bytes;
// append to data cache
memcpy(&wr_data->cached_data.data8[wr_data->cached_bytes], data, wr_sz);
wr_data->cached_bytes += wr_sz;
if (wr_data->cached_bytes == sizeof(wr_data->cached_data)) {
err = ESP_COREDUMP_FLASH_WRITE(s_core_flash_config.partition.start + wr_data->off, &wr_data->cached_data, sizeof(wr_data->cached_data));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write cached data to flash (%d)!", err);
return err;
}
// update checksum according to padding
esp_core_dump_checksum_update(wr_data, &wr_data->cached_data, sizeof(wr_data->cached_data));
// reset data cache
wr_data->cached_bytes = 0;
memset(&wr_data->cached_data, 0, sizeof(wr_data->cached_data));
}
wr_data->off += sizeof(wr_data->cached_data);
written += wr_sz;
data_size -= wr_sz;
}
len = data_size % sizeof(uint32_t);
if (len) {
// write last bytes with padding, actual TCB len can be retrieved by esptool from core dump header
rom_data.data32 = 0;
for (k = 0; k < len; k++) {
rom_data.data8[k] = *(data + data_len + k);
}
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
err = spi_flash_write(off + data_len, &rom_data, sizeof(uint32_t));
#else
err = esp_flash_write(esp_flash_default_chip, &rom_data, off + data_len, sizeof(uint32_t));
#endif
wr_sz = (data_size / sizeof(wr_data->cached_data)) * sizeof(wr_data->cached_data);
if (wr_sz) {
err = ESP_COREDUMP_FLASH_WRITE(s_core_flash_config.partition.start + wr_data->off, data + written, wr_sz);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to finish write data to flash (%d)!", err);
return 0;
ESP_COREDUMP_LOGE("Failed to write data to flash (%d)!", err);
return err;
}
data_len += sizeof(uint32_t);
// update checksum of data written
esp_core_dump_checksum_update(wr_data, data + written, wr_sz);
wr_data->off += wr_sz;
written += wr_sz;
data_size -= wr_sz;
}
return data_len;
if (data_size > 0) {
// append to data cache
memcpy(&wr_data->cached_data, data + written, data_size);
wr_data->cached_bytes = data_size;
}
return ESP_OK;
}
static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_len)
{
esp_err_t err;
uint32_t sec_num;
core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
uint32_t cs_len;
cs_len = esp_core_dump_checksum_finish(wr_data, NULL);
// check for available space in partition
if ((*data_len + sizeof(uint32_t)) > s_core_flash_config.partition.size) {
if ((*data_len + cs_len) > s_core_flash_config.partition.size) {
ESP_COREDUMP_LOGE("Not enough space to save core dump!");
return ESP_ERR_NO_MEM;
}
// add space for CRC
*data_len += sizeof(core_dump_crc_t);
// add space for checksum
*data_len += cs_len;
memset(wr_data, 0, sizeof(*wr_data));
memset(wr_data, 0, sizeof(core_dump_write_data_t));
sec_num = *data_len / SPI_FLASH_SEC_SIZE;
if (*data_len % SPI_FLASH_SEC_SIZE) {
sec_num++;
}
ESP_COREDUMP_LOGI("Erase flash %d bytes @ 0x%x", sec_num * SPI_FLASH_SEC_SIZE, s_core_flash_config.partition.start + 0);
assert(sec_num * SPI_FLASH_SEC_SIZE <= s_core_flash_config.partition.size);
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
err = spi_flash_erase_range(s_core_flash_config.partition.start + 0, sec_num * SPI_FLASH_SEC_SIZE);
#else
err = esp_flash_erase_region(esp_flash_default_chip, s_core_flash_config.partition.start + 0, sec_num * SPI_FLASH_SEC_SIZE);
#endif
err = ESP_COREDUMP_FLASH_ERASE(s_core_flash_config.partition.start + 0, sec_num * SPI_FLASH_SEC_SIZE);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to erase flash (%d)!", err);
return err;
@ -150,81 +160,70 @@ static esp_err_t esp_core_dump_flash_write_prepare(void *priv, uint32_t *data_le
return err;
}
static esp_err_t esp_core_dump_flash_write_word(core_dump_write_flash_data_t *wr_data, uint32_t word)
{
esp_err_t err = ESP_OK;
uint32_t data32 = word;
assert(wr_data->off + sizeof(uint32_t) <= s_core_flash_config.partition.size);
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
err = spi_flash_write(s_core_flash_config.partition.start + wr_data->off, &data32, sizeof(uint32_t));
#else
err = esp_flash_write(esp_flash_default_chip, &data32, s_core_flash_config.partition.start + wr_data->off, sizeof(uint32_t));
#endif
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to write to flash (%d)!", err);
return err;
}
wr_data->off += sizeof(uint32_t);
return err;
}
static esp_err_t esp_core_dump_flash_write_start(void *priv)
{
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
esp_core_dump_checksum_init(wr_data);
return ESP_OK;
}
static esp_err_t esp_core_dump_flash_write_end(void *priv)
{
core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv;
esp_err_t err;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
void* checksum;
uint32_t cs_len = esp_core_dump_checksum_finish(wr_data, &checksum);
// flush cached bytes with zero padding
if (wr_data->cached_bytes) {
err = ESP_COREDUMP_FLASH_WRITE(s_core_flash_config.partition.start + wr_data->off, &wr_data->cached_data, sizeof(wr_data->cached_data));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
return err;
}
// update checksum according to padding
esp_core_dump_checksum_update(wr_data, &wr_data->cached_data, sizeof(wr_data->cached_data));
wr_data->off += sizeof(wr_data->cached_data);
}
err = ESP_COREDUMP_FLASH_WRITE(s_core_flash_config.partition.start + wr_data->off, checksum, cs_len);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to flush cached data to flash (%d)!", err);
return err;
}
wr_data->off += cs_len;
ESP_COREDUMP_LOGI("Write end offset 0x%x, check sum length %d", wr_data->off, cs_len);
#if LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG
union
{
uint8_t data8[16];
uint32_t data32[4];
uint8_t data8[sizeof(core_dump_header_t)];
uint32_t data32[sizeof(core_dump_header_t)/sizeof(uint32_t)];
} rom_data;
#ifdef CONFIG_SPI_FLASH_USE_LEGACY_IMPL
esp_err_t err = spi_flash_read(s_core_flash_config.partition.start + 0, &rom_data, sizeof(rom_data));
#else
esp_err_t err = esp_flash_read(esp_flash_default_chip, &rom_data, s_core_flash_config.partition.start + 0, sizeof(rom_data));
#endif
err = ESP_COREDUMP_FLASH_READ(s_core_flash_config.partition.start + 0, &rom_data, sizeof(rom_data));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to read flash (%d)!", err);
ESP_COREDUMP_LOGE("Failed to read back coredump header (%d)!", err);
return err;
} else {
ESP_COREDUMP_LOG_PROCESS("Data from flash:");
for (uint32_t i = 0; i < sizeof(rom_data)/sizeof(rom_data.data32[0]); i++) {
ESP_COREDUMP_LOG_PROCESS("%x", rom_data.data32[i]);
ESP_COREDUMP_LOG_PROCESS("Core dump header words from flash:");
for (uint32_t i = 0; i < sizeof(rom_data)/sizeof(uint32_t); i++) {
ESP_COREDUMP_LOG_PROCESS("0x%x", rom_data.data32[i]);
}
}
#endif
// write core dump CRC
ESP_COREDUMP_LOG_PROCESS("Dump data CRC = 0x%x", wr_data->crc);
return esp_core_dump_flash_write_word(wr_data, wr_data->crc);
}
static esp_err_t esp_core_dump_flash_write_data(void *priv, void * data, uint32_t data_len)
{
esp_err_t err = ESP_OK;
core_dump_write_flash_data_t *wr_data = (core_dump_write_flash_data_t *)priv;
uint32_t len = esp_core_dump_write_flash_padded(s_core_flash_config.partition.start + wr_data->off, data, data_len);
if (len != data_len) {
return ESP_FAIL;
uint32_t crc;
err = ESP_COREDUMP_FLASH_READ(s_core_flash_config.partition.start + wr_data->off - cs_len, &crc, sizeof(crc));
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Failed to read back checksum word (%d)!", err);
return err;
} else {
ESP_COREDUMP_LOG_PROCESS("Checksum word from flash: 0x%x @ 0x%x", crc, wr_data->off - cs_len);
}
wr_data->off += len;
wr_data->crc = crc32_le(wr_data->crc, data, data_len);
#endif
return err;
}
void esp_core_dump_to_flash(XtExcFrame *frame)
void esp_core_dump_to_flash(void *frame)
{
static core_dump_write_config_t wr_cfg;
static core_dump_write_flash_data_t wr_data;
static core_dump_write_data_t wr_data;
core_dump_crc_t crc = esp_core_dump_calc_flash_config_crc();
if (s_core_flash_config.partition_config_crc != crc) {
@ -245,11 +244,11 @@ void esp_core_dump_to_flash(XtExcFrame *frame)
wr_cfg.prepare = esp_core_dump_flash_write_prepare;
wr_cfg.start = esp_core_dump_flash_write_start;
wr_cfg.end = esp_core_dump_flash_write_end;
wr_cfg.write = esp_core_dump_flash_write_data;
wr_cfg.write = (esp_core_dump_flash_write_data_t)esp_core_dump_flash_write_data;
wr_cfg.priv = &wr_data;
ESP_COREDUMP_LOGI("Save core dump to flash...");
esp_core_dump_write((void*)frame, &wr_cfg);
esp_core_dump_write(frame, &wr_cfg);
ESP_COREDUMP_LOGI("Core dump has been saved to flash.");
}
#endif

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2019 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.
@ -14,92 +14,544 @@
#include <string.h>
#include <stdbool.h>
#include "soc/soc_memory_layout.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/xtensa_context.h" // for exception register stack structure
#include "esp_core_dump_priv.h"
const static DRAM_ATTR char TAG[] __attribute__((unused)) = "esp_core_dump_port";
#define COREDUMP_EM_XTENSA 0x5E
#define COREDUMP_INVALID_CAUSE_VALUE 0xFFFF
#define COREDUMP_EXTRA_REG_NUM 16
#define COREDUMP_FAKE_STACK_START 0x20000000
#define COREDUMP_FAKE_STACK_LIMIT 0x30000000
#define COREDUMP_GET_REG_PAIR(reg_idx, reg_ptr) { *(uint32_t*)(reg_ptr++) = (uint32_t)reg_idx; \
RSR(reg_idx, *(uint32_t*)(reg_ptr++)); \
}
// Enumeration of registers of exception stack frame
// and solicited stack frame
typedef enum
{
// XT_SOL_EXIT = 0,
XT_SOL_PC = 1,
XT_SOL_PS = 2,
// XT_SOL_NEXT = 3,
XT_SOL_AR_START = 4,
XT_SOL_AR_NUM = 4,
// XT_SOL_FRMSZ = 8,
XT_STK_EXIT = 0,
XT_STK_PC = 1,
XT_STK_PS = 2,
XT_STK_AR_START = 3,
XT_STK_AR_NUM = 16,
XT_STK_SAR = 19,
XT_STK_EXCCAUSE = 20,
XT_STK_EXCVADDR = 21,
XT_STK_LBEG = 22,
XT_STK_LEND = 23,
XT_STK_LCOUNT = 24,
//XT_STK_FRMSZ = 25,
} stk_frame_t;
// Xtensa ELF core file register set representation ('.reg' section).
// Copied from target-side ELF header <xtensa/elf.h>.
typedef struct
{
uint32_t pc;
uint32_t ps;
uint32_t lbeg;
uint32_t lend;
uint32_t lcount;
uint32_t sar;
uint32_t windowstart;
uint32_t windowbase;
uint32_t reserved[8+48];
uint32_t ar[XCHAL_NUM_AREGS];
} __attribute__((packed)) xtensa_gregset_t;
typedef struct
{
uint32_t reg_index;
uint32_t reg_val;
} __attribute__((packed)) core_dump_reg_pair_t;
typedef struct
{
uint32_t crashed_task_tcb;
core_dump_reg_pair_t exccause;
core_dump_reg_pair_t excvaddr;
core_dump_reg_pair_t extra_regs[COREDUMP_EXTRA_REG_NUM];
} __attribute__((packed)) xtensa_extra_info_t;
// Xtensa Program Status for GDB
typedef struct
{
uint32_t si_signo;
uint32_t si_code;
uint32_t si_errno;
uint16_t pr_cursig;
uint16_t pr_pad0;
uint32_t pr_sigpend;
uint32_t pr_sighold;
uint32_t pr_pid;
uint32_t pr_ppid;
uint32_t pr_pgrp;
uint32_t pr_sid;
uint64_t pr_utime;
uint64_t pr_stime;
uint64_t pr_cutime;
uint64_t pr_cstime;
} __attribute__((packed)) xtensa_pr_status_t;
typedef struct
{
xtensa_pr_status_t pr_status;
xtensa_gregset_t regs;
// Todo: acc to xtensa_gregset_t number of regs must be 128,
// but gdb complains when it less than 129
uint32_t reserved;
} __attribute__((packed)) xtensa_elf_reg_dump_t;
extern uint8_t port_IntStack;
#if CONFIG_ESP32_ENABLE_COREDUMP
inline bool esp_task_stack_start_is_sane(uint32_t sp)
static uint32_t s_total_length = 0;
static XtExcFrame s_fake_stack_frame = {
.pc = (UBaseType_t) COREDUMP_FAKE_STACK_START, // task entrypoint fake_ptr
.a0 = (UBaseType_t) 0, // to terminate GDB backtrace
.a1 = (UBaseType_t) (COREDUMP_FAKE_STACK_START + sizeof(XtExcFrame)), // physical top of stack frame
.exit = (UBaseType_t) 0, // user exception exit dispatcher
.ps = (PS_UM | PS_EXCM),
.exccause = (UBaseType_t) COREDUMP_INVALID_CAUSE_VALUE,
};
static uint32_t s_fake_stacks_num;
static xtensa_extra_info_t s_extra_info;
#if ESP32_CORE_DUMP_STACK_SIZE > 0
uint8_t s_coredump_stack[ESP32_CORE_DUMP_STACK_SIZE];
uint8_t *s_core_dump_sp;
static uint32_t esp_core_dump_free_stack_space(const uint8_t *pucStackByte)
{
return !(sp < 0x3ffae010UL || sp > 0x3fffffffUL);
uint32_t ulCount = 0U;
while( *pucStackByte == (uint8_t)COREDUMP_STACK_FILL_BYTE ) {
pucStackByte -= portSTACK_GROWTH;
ulCount++;
}
ulCount /= (uint32_t)sizeof(uint8_t);
return (uint32_t)ulCount;
}
#endif
void esp_core_dump_report_stack_usage(void)
{
#if ESP32_CORE_DUMP_STACK_SIZE > 0
uint32_t bytes_free = esp_core_dump_free_stack_space(s_coredump_stack);
ESP_COREDUMP_LOGD("Core dump used %u bytes on stack. %u bytes left free.",
s_core_dump_sp - s_coredump_stack - bytes_free, bytes_free);
#endif
}
inline bool esp_tcb_addr_is_sane(uint32_t addr, uint32_t sz)
#if CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
// function to calculate SHA256 for solid data array
int esp_core_dump_sha(mbedtls_sha256_context *ctx,
const unsigned char *input,
size_t ilen,
unsigned char output[32] )
{
//TODO: currently core dump supports TCBs in DRAM only, external SRAM not supported yet
return !(addr < 0x3ffae000UL || (addr + sz) > 0x40000000UL);
assert(input);
mbedtls_sha256_init(ctx);
if((mbedtls_sha256_starts_ret(ctx, 0) != 0)) goto exit;
#if CONFIG_MBEDTLS_HARDWARE_SHA
// set software mode for SHA calculation
ctx->mode = ESP_MBEDTLS_SHA256_SOFTWARE;
#endif
if((mbedtls_sha256_update_ret(ctx, input, ilen) != 0)) goto exit;
if((mbedtls_sha256_finish_ret(ctx, output) != 0)) goto exit;
esp_core_dump_print_sha256(DRAM_STR("Coredump SHA256"), (void*)output);
s_total_length = ilen;
exit:
mbedtls_sha256_free(ctx);
return ilen;
}
void esp_core_dump_print_sha256(const char* msg, const uint8_t* sha_output)
{
ets_printf(DRAM_STR("%s='"), msg);
for (int i = 0; i < COREDUMP_SHA256_LEN; i++) {
ets_printf(DRAM_STR("%02x"), sha_output[i]);
}
ets_printf(DRAM_STR("'\r\n"));
}
#endif
void esp_core_dump_checksum_init(core_dump_write_data_t* wr_data)
{
if (wr_data) {
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
wr_data->crc = 0;
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
mbedtls_sha256_init(&wr_data->ctx);
(void)mbedtls_sha256_starts_ret(&wr_data->ctx, 0);
#endif
s_total_length = 0;
}
}
void esp_core_dump_checksum_update(core_dump_write_data_t* wr_data, void* data, size_t data_len)
{
if (wr_data && data) {
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
wr_data->crc = crc32_le(wr_data->crc, data, data_len);
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
#if CONFIG_MBEDTLS_HARDWARE_SHA
// set software mode of SHA calculation
wr_data->ctx.mode = ESP_MBEDTLS_SHA256_SOFTWARE;
#endif
(void)mbedtls_sha256_update_ret(&wr_data->ctx, data, data_len);
#endif
s_total_length += data_len; // keep counter of cashed bytes
} else {
ESP_COREDUMP_LOGE("Wrong write data info!");
}
}
uint32_t esp_core_dump_checksum_finish(core_dump_write_data_t* wr_data, void** chs_ptr)
{
// get core dump checksum
uint32_t chs_len = 0;
#if CONFIG_ESP32_COREDUMP_CHECKSUM_CRC32
if (chs_ptr) {
wr_data->crc = wr_data->crc;
*chs_ptr = (void*)&wr_data->crc;
ESP_COREDUMP_LOG_PROCESS("Dump data CRC = 0x%x, offset = 0x%x", wr_data->crc, wr_data->off);
}
chs_len = sizeof(wr_data->crc);
#elif CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
if (chs_ptr) {
ESP_COREDUMP_LOG_PROCESS("Dump data offset = %d", wr_data->off);
(void)mbedtls_sha256_finish_ret(&wr_data->ctx, (uint8_t*)&wr_data->sha_output);
*chs_ptr = (void*)&wr_data->sha_output[0];
mbedtls_sha256_free(&wr_data->ctx);
}
chs_len = sizeof(wr_data->sha_output);
#endif
ESP_COREDUMP_LOG_PROCESS("Total length of hashed data: %d!", s_total_length);
return chs_len;
}
inline uint16_t esp_core_dump_get_arch_id()
{
return COREDUMP_EM_XTENSA;
}
inline bool esp_core_dump_mem_seg_is_sane(uint32_t addr, uint32_t sz)
{
//TODO: currently core dump supports memory segments in DRAM only, external SRAM not supported yet
return esp_ptr_in_dram((void *)addr) && esp_ptr_in_dram((void *)(addr+sz-1));
}
inline bool esp_core_dump_task_stack_end_is_sane(uint32_t sp)
{
//TODO: currently core dump supports stacks in DRAM only, external SRAM not supported yet
return esp_ptr_in_dram((void *)sp);
}
inline bool esp_core_dump_tcb_addr_is_sane(uint32_t addr)
{
return esp_core_dump_mem_seg_is_sane(addr, COREDUMP_TCB_SIZE);
}
uint32_t esp_core_dump_get_tasks_snapshot(core_dump_task_header_t* const tasks,
const uint32_t snapshot_size, uint32_t* const tcb_sz)
const uint32_t snapshot_size)
{
uint32_t task_num = (uint32_t)uxTaskGetSnapshotAll((TaskSnapshot_t*)tasks, (UBaseType_t)snapshot_size, (UBaseType_t*)tcb_sz);
uint32_t tcb_sz; // unused
uint32_t task_num = (uint32_t)uxTaskGetSnapshotAll((TaskSnapshot_t*)tasks,
(UBaseType_t)snapshot_size,
(UBaseType_t*)&tcb_sz);
return task_num;
}
bool esp_core_dump_process_tcb(void *frame, core_dump_task_header_t *task_snaphort, uint32_t tcb_sz)
inline uint32_t esp_core_dump_get_isr_stack_end(void)
{
XtExcFrame *exc_frame = (XtExcFrame*)frame;
return (uint32_t)((uint8_t *)&port_IntStack + (xPortGetCoreID()+1)*configISR_STACK_SIZE);
}
if (!esp_tcb_addr_is_sane((uint32_t)task_snaphort->tcb_addr, tcb_sz)) {
ESP_COREDUMP_LOG_PROCESS("Bad TCB addr %x!", task_snaphort->tcb_addr);
uint32_t esp_core_dump_get_stack(core_dump_task_header_t *task_snapshot,
uint32_t *stk_vaddr, uint32_t *stk_len)
{
if (task_snapshot->stack_end > task_snapshot->stack_start) {
*stk_len = task_snapshot->stack_end - task_snapshot->stack_start;
*stk_vaddr = task_snapshot->stack_start;
} else {
*stk_len = task_snapshot->stack_start - task_snapshot->stack_end;
*stk_vaddr = task_snapshot->stack_end;
}
if (*stk_vaddr >= COREDUMP_FAKE_STACK_START && *stk_vaddr < COREDUMP_FAKE_STACK_LIMIT) {
return (uint32_t)&s_fake_stack_frame;
}
return *stk_vaddr;
}
// The function creates small fake stack for task as deep as exception frame size
// It is required for gdb to take task into account but avoid back trace of stack.
// The espcoredump.py script is able to recognize that task is broken
static void *esp_core_dump_get_fake_stack(uint32_t *stk_len)
{
*stk_len = sizeof(s_fake_stack_frame);
return (uint8_t*)COREDUMP_FAKE_STACK_START + sizeof(s_fake_stack_frame)*s_fake_stacks_num++;
}
static core_dump_reg_pair_t *esp_core_dump_get_epc_regs(core_dump_reg_pair_t* src)
{
uint32_t* reg_ptr = (uint32_t*)src;
// get InterruptException program counter registers
COREDUMP_GET_REG_PAIR(EPC_1, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_2, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_3, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_4, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_5, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_6, reg_ptr);
COREDUMP_GET_REG_PAIR(EPC_7, reg_ptr);
return (core_dump_reg_pair_t*)reg_ptr;
}
static core_dump_reg_pair_t *esp_core_dump_get_eps_regs(core_dump_reg_pair_t* src)
{
uint32_t* reg_ptr = (uint32_t*)src;
// get InterruptException processor state registers
COREDUMP_GET_REG_PAIR(EPS_2, reg_ptr);
COREDUMP_GET_REG_PAIR(EPS_3, reg_ptr);
COREDUMP_GET_REG_PAIR(EPS_4, reg_ptr);
COREDUMP_GET_REG_PAIR(EPS_5, reg_ptr);
COREDUMP_GET_REG_PAIR(EPS_6, reg_ptr);
COREDUMP_GET_REG_PAIR(EPS_7, reg_ptr);
return (core_dump_reg_pair_t*)reg_ptr;
}
// Returns list of registers (in GDB format) from xtensa stack frame
static esp_err_t esp_core_dump_get_regs_from_stack(void* stack_addr,
size_t size,
xtensa_gregset_t* regs)
{
XtExcFrame* exc_frame = (XtExcFrame*)stack_addr;
uint32_t* stack_arr = (uint32_t*)stack_addr;
if (size < sizeof(XtExcFrame)) {
ESP_COREDUMP_LOGE("Too small stack to keep frame: %d bytes!", size);
return ESP_FAIL;
}
// Stack frame type indicator is always the first item
uint32_t rc = exc_frame->exit;
// is this current crashed task?
if (rc == COREDUMP_CURR_TASK_MARKER)
{
s_extra_info.exccause.reg_val = exc_frame->exccause;
s_extra_info.exccause.reg_index = EXCCAUSE;
s_extra_info.excvaddr.reg_val = exc_frame->excvaddr;
s_extra_info.excvaddr.reg_index = EXCVADDR;
// get InterruptException registers into extra_info
core_dump_reg_pair_t *regs_ptr = esp_core_dump_get_eps_regs(s_extra_info.extra_regs);
esp_core_dump_get_epc_regs(regs_ptr);
} else {
// initialize EXCCAUSE and EXCVADDR members of frames for all the tasks,
// except for the crashed one
exc_frame->exccause = COREDUMP_INVALID_CAUSE_VALUE;
exc_frame->excvaddr = 0;
}
if (rc != 0) {
regs->pc = exc_frame->pc;
regs->ps = exc_frame->ps;
for (int i = 0; i < XT_STK_AR_NUM; i++) {
regs->ar[i] = stack_arr[XT_STK_AR_START + i];
}
regs->sar = exc_frame->sar;
#if CONFIG_IDF_TARGET_ESP32
regs->lbeg = exc_frame->lbeg;
regs->lend = exc_frame->lend;
regs->lcount = exc_frame->lcount;
#endif
// FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set
// and GDB can not unwind callstack properly (it implies not windowed call0)
if (regs->ps & PS_UM) {
regs->ps &= ~PS_EXCM;
}
} else {
regs->pc = stack_arr[XT_SOL_PC];
regs->ps = stack_arr[XT_SOL_PS];
for (int i = 0; i < XT_SOL_AR_NUM; i++) {
regs->ar[i] = stack_arr[XT_SOL_AR_START + i];
}
regs->pc = (regs->pc & 0x3fffffff);
if (regs->pc & 0x80000000) {
regs->pc = (regs->pc & 0x3fffffff);
}
if (regs->ar[0] & 0x80000000) {
regs->ar[0] = (regs->ar[0] & 0x3fffffff);
}
}
return ESP_OK;
}
uint32_t esp_core_dump_get_task_regs_dump(core_dump_task_header_t *task, void **reg_dump)
{
uint32_t stack_vaddr, stack_paddr, stack_len;
static xtensa_elf_reg_dump_t s_reg_dump = { 0 };
stack_paddr = esp_core_dump_get_stack(task, &stack_vaddr, &stack_len);
ESP_COREDUMP_LOG_PROCESS("Add regs for task 0x%x", task->tcb_addr);
// initialize program status for the task
s_reg_dump.pr_status.pr_cursig = 0;
s_reg_dump.pr_status.pr_pid = (uint32_t)task->tcb_addr;
// fill the gdb registers structure from stack
esp_err_t err = esp_core_dump_get_regs_from_stack((void*)stack_paddr,
stack_len,
&s_reg_dump.regs);
if (err != ESP_OK) {
ESP_COREDUMP_LOGE("Error while registers processing.");
}
*reg_dump = &s_reg_dump;
return sizeof(s_reg_dump);
}
inline void* esp_core_dump_get_current_task_handle()
{
return (void*)xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
}
bool esp_core_dump_check_task(void *frame,
core_dump_task_header_t *task,
bool* is_current,
bool* stack_is_valid)
{
XtExcFrame *exc_frame = frame;
bool is_curr_task = false;
bool stack_is_sane = false;
uint32_t stk_size = 0;
if (!esp_core_dump_tcb_addr_is_sane((uint32_t)task->tcb_addr)) {
ESP_COREDUMP_LOG_PROCESS("Bad TCB addr=%x!", task->tcb_addr);
return false;
}
if (task_snaphort->tcb_addr == xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID())) {
// Set correct stack top for current task
task_snaphort->stack_start = (uint32_t)exc_frame;
// This field is not initialized for crashed task, but stack frame has the structure of interrupt one,
// so make workaround to allow espcoredump to parse it properly.
if (exc_frame->exit == 0)
exc_frame->exit = -1;
ESP_COREDUMP_LOG_PROCESS("Current task %x EXIT/PC/PS/A0/SP %x %x %x %x %x",
task_snaphort->tcb_addr, exc_frame->exit, exc_frame->pc, exc_frame->ps, exc_frame->a0, exc_frame->a1);
}
else {
XtSolFrame *task_frame = (XtSolFrame *)task_snaphort->stack_start;
if (task_frame->exit == 0) {
ESP_COREDUMP_LOG_PROCESS("Task %x EXIT/PC/PS/A0/SP %x %x %x %x %x",
task_snaphort->tcb_addr, task_frame->exit, task_frame->pc, task_frame->ps, task_frame->a0, task_frame->a1);
is_curr_task = task->tcb_addr == esp_core_dump_get_current_task_handle();
if (is_curr_task) {
// Set correct stack top for current task; only modify if we came from the task,
// and not an ISR that crashed.
if (!xPortInterruptedFromISRContext()) {
task->stack_start = (uint32_t)exc_frame;
}
else {
exc_frame->exit = COREDUMP_CURR_TASK_MARKER;
s_extra_info.crashed_task_tcb = (uint32_t)task->tcb_addr;
}
stack_is_sane = esp_core_dump_check_stack(task->stack_start, task->stack_end);
if (!stack_is_sane) {
// Skip saving of invalid task if stack corrupted
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x), stack is corrupted (%x, %x)",
task->tcb_addr,
task->stack_start,
task->stack_end);
task->stack_start = (uint32_t)esp_core_dump_get_fake_stack(&stk_size);
task->stack_end = (uint32_t)(task->stack_start + stk_size);
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x), use start, end (%x, %x)",
task->tcb_addr,
task->stack_start,
task->stack_end);
}
if (is_curr_task) {
if (!stack_is_sane)
ESP_COREDUMP_LOG_PROCESS("Current task 0x%x is broken!", task->tcb_addr);
ESP_COREDUMP_LOG_PROCESS("Current task (TCB:%x), EXIT/PC/PS/A0/SP %x %x %x %x %x",
task->tcb_addr,
exc_frame->exit,
exc_frame->pc,
exc_frame->ps,
exc_frame->a0,
exc_frame->a1);
} else {
XtSolFrame *task_frame = (XtSolFrame *)task->stack_start;
if (stack_is_sane) {
if (task_frame->exit == 0) {
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x), EXIT/PC/PS/A0/SP %x %x %x %x %x",
task->tcb_addr,
task_frame->exit,
task_frame->pc,
task_frame->ps,
task_frame->a0,
task_frame->a1);
} else {
#if CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH
XtExcFrame *task_frame2 = (XtExcFrame *)task_snaphort->stack_start;
ESP_COREDUMP_LOG_PROCESS("Task %x EXIT/PC/PS/A0/SP %x %x %x %x %x",
task_snaphort->tcb_addr, task_frame2->exit, task_frame2->pc, task_frame2->ps, task_frame2->a0, task_frame2->a1);
XtExcFrame *task_frame2 = (XtExcFrame *)task->stack_start;
task_frame2->exccause = COREDUMP_INVALID_CAUSE_VALUE;
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x) EXIT/PC/PS/A0/SP %x %x %x %x %x",
task->tcb_addr,
task_frame2->exit,
task_frame2->pc,
task_frame2->ps,
task_frame2->a0,
task_frame2->a1);
#endif
}
} else {
ESP_COREDUMP_LOG_PROCESS("Task (TCB:%x), stack_start=%x is incorrect, skip registers printing.",
task->tcb_addr, task->stack_start);
}
}
if (is_current) {
*is_current = is_curr_task;
}
if (stack_is_valid) {
*stack_is_valid = stack_is_sane;
}
return true;
}
bool esp_core_dump_process_stack(core_dump_task_header_t* task_snaphort, uint32_t *length)
bool esp_core_dump_check_stack(uint32_t stack_start, uint32_t stack_end)
{
uint32_t len = 0;
uint32_t len = stack_end - stack_start;
bool task_is_valid = false;
len = (uint32_t)task_snaphort->stack_end - (uint32_t)task_snaphort->stack_start;
// Check task's stack
if (!esp_stack_ptr_is_sane(task_snaphort->stack_start) ||
!esp_task_stack_start_is_sane((uint32_t)task_snaphort->stack_end) ||
if (!esp_stack_ptr_is_sane(stack_start) || !esp_core_dump_task_stack_end_is_sane(stack_end) ||
(len > COREDUMP_MAX_TASK_STACK_SIZE)) {
// Check if current task stack corrupted
if (task_snaphort->tcb_addr == xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID())) {
ESP_COREDUMP_LOG_PROCESS("Crashed task will be skipped!");
}
ESP_COREDUMP_LOG_PROCESS("Corrupted TCB %x: stack len %lu, top %x, end %x!",
task_snaphort->tcb_addr, len, task_snaphort->stack_start, task_snaphort->stack_end);
task_snaphort->tcb_addr = 0; // make TCB addr invalid to skip it in dump
// Check if current task stack is corrupted
task_is_valid = false;
} else {
ESP_COREDUMP_LOG_PROCESS("Stack len = %lu (%x %x)", len,
task_snaphort->stack_start, task_snaphort->stack_end);
// Take stack padding into account
if (length) {
*length = (len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1);
}
ESP_COREDUMP_LOG_PROCESS("Stack len = %lu (%x %x)", len, stack_start, stack_end);
task_is_valid = true;
}
return task_is_valid;
}
void esp_core_dump_init_extra_info()
{
s_extra_info.crashed_task_tcb = COREDUMP_CURR_TASK_MARKER;
// Initialize exccause register to default value (required if current task corrupted)
s_extra_info.exccause.reg_val = COREDUMP_INVALID_CAUSE_VALUE;
s_extra_info.exccause.reg_index = EXCCAUSE;
}
uint32_t esp_core_dump_get_extra_info(void **info)
{
*info = &s_extra_info;
return sizeof(s_extra_info);
}
#endif

View file

@ -1,4 +1,4 @@
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
// Copyright 2015-2019 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.
@ -16,9 +16,12 @@
#include "soc/gpio_periph.h"
#include "driver/gpio.h"
#include "esp_core_dump_priv.h"
// TODO: move chip dependent part to portable code
#if CONFIG_IDF_TARGET_ESP32
#include "esp32/rom/crc.h"
#include "esp32/clk.h"
#elif CONFIG_IDF_TARGET_ESP32S2BETA
#include "esp32s2beta/rom/crc.h"
#include "esp32s2beta/clk.h"
#endif
@ -54,22 +57,49 @@ static void esp_core_dump_b64_encode(const uint8_t *src, uint32_t src_len, uint8
static esp_err_t esp_core_dump_uart_write_start(void *priv)
{
esp_err_t err = ESP_OK;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
esp_core_dump_checksum_init(wr_data);
ets_printf(DRAM_STR("================= CORE DUMP START =================\r\n"));
return err;
}
static esp_err_t esp_core_dump_uart_write_prepare(void *priv, uint32_t *data_len)
{
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
uint32_t cs_len;
cs_len = esp_core_dump_checksum_finish(wr_data, NULL);
*data_len += cs_len;
return ESP_OK;
}
static esp_err_t esp_core_dump_uart_write_end(void *priv)
{
esp_err_t err = ESP_OK;
char buf[64 + 4];
void* cs_addr = NULL;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
if (wr_data) {
size_t cs_len = esp_core_dump_checksum_finish(wr_data, &cs_addr);
wr_data->off += cs_len;
esp_core_dump_b64_encode((const uint8_t *)cs_addr, cs_len, (uint8_t*)&buf[0]);
ets_printf(DRAM_STR("%s\r\n"), buf);
}
ets_printf(DRAM_STR("================= CORE DUMP END =================\r\n"));
#if CONFIG_ESP32_COREDUMP_CHECKSUM_SHA256
if (cs_addr) {
esp_core_dump_print_sha256(DRAM_STR("Coredump SHA256"), (uint8_t*)(cs_addr));
}
#endif
return err;
}
static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t data_len)
{
esp_err_t err = ESP_OK;
char buf[64 + 4], *addr = data;
char buf[64 + 4];
char *addr = data;
char *end = addr + data_len;
core_dump_write_data_t *wr_data = (core_dump_write_data_t *)priv;
while (addr < end) {
size_t len = end - addr;
@ -82,6 +112,10 @@ static esp_err_t esp_core_dump_uart_write_data(void *priv, void * data, uint32_t
ets_printf(DRAM_STR("%s\r\n"), buf);
}
if (wr_data) {
wr_data->off += data_len;
esp_core_dump_checksum_update(wr_data, data, data_len);
}
return err;
}
@ -99,16 +133,18 @@ static int esp_core_dump_uart_get_char(void) {
void esp_core_dump_to_uart(XtExcFrame *frame)
{
core_dump_write_config_t wr_cfg;
core_dump_write_data_t wr_data;
uint32_t tm_end, tm_cur;
int ch;
memset(&wr_cfg, 0, sizeof(wr_cfg));
wr_cfg.prepare = NULL;
wr_cfg.prepare = esp_core_dump_uart_write_prepare;
wr_cfg.start = esp_core_dump_uart_write_start;
wr_cfg.end = esp_core_dump_uart_write_end;
wr_cfg.write = esp_core_dump_uart_write_data;
wr_cfg.priv = NULL;
wr_cfg.priv = (void*)&wr_data;
// TODO: move chip dependent code to portable part
//Make sure txd/rxd are enabled
// use direct reg access instead of gpio_pullup_dis which can cause exception when flash cache is disabled
REG_CLR_BIT(GPIO_PIN_REG_1, FUN_PU);

View file

@ -1,192 +1,369 @@
YCEAAAEAAAAKAAAAfAEAAA==
dFT7PwCd+z/0nvs/
cJ37P5Ce+z/cHQAAeC/7P3gv+z90VPs/cC/7PxIAAADOzs7Ozs7OznRU+z8AAAAA
BwAAAPiW+z91bmFsaWduZWRfcHRyX3QAAQAAAPSe+z8AAAAAIAAGAA8AAADOzs7O
BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
UD0AAAIAAAAKAAAAfAEAAAAAAAA=
f0VMRgEBAQAAAAAAAAAAAAQAXgABAAAAAAAAADQAAAAAAAAAAAAAADQAIAAWACgA
AAAAAA==
BAAAAPQCAAAAAAAAAAAAAMAXAADAFwAABgAAAAAAAAA=
AQAAALQaAACgavs/oGr7P3wBAAB8AQAABgAAAAAAAAA=
AQAAADAcAAAgtPs/ILT7P/gBAAD4AQAABgAAAAAAAAA=
AQAAACgeAACQrPs/kKz7P3wBAAB8AQAABgAAAAAAAAA=
AQAAAKQfAABwqfs/cKn7PwwDAAAMAwAABgAAAAAAAAA=
AQAAALAiAABMgPs/TID7P3wBAAB8AQAABgAAAAAAAAA=
AQAAACwkAACQfvs/kH77P6gBAACoAQAABgAAAAAAAAA=
AQAAANQlAACwePs/sHj7P3wBAAB8AQAABgAAAAAAAAA=
AQAAAFAnAADwdvs/8Hb7P6wBAACsAQAABgAAAAAAAAA=
AQAAAPwoAAAUafs/FGn7P3wBAAB8AQAABgAAAAAAAAA=
AQAAAHgqAABgZ/s/YGf7P6ABAACgAQAABgAAAAAAAAA=
AQAAABgsAACYbPs/mGz7P3wBAAB8AQAABgAAAAAAAAA=
AQAAAJQtAACAvPs/gLz7P6gBAACoAQAABgAAAAAAAAA=
AQAAADwvAAD0ivs/9Ir7P3wBAAB8AQAABgAAAAAAAAA=
AQAAALgwAAAgifs/IIn7P8ABAADAAQAABgAAAAAAAAA=
AQAAAHgyAAA0+/o/NPv6P3wBAAB8AQAABgAAAAAAAAA=
AQAAAPQzAABg+fo/YPn6P8ABAADAAQAABgAAAAAAAAA=
AQAAALQ1AABwWPs/cFj7P3wBAAB8AQAABgAAAAAAAAA=
AQAAADA3AACwVvs/sFb7P6wBAACsAQAABgAAAAAAAAA=
AQAAANw4AACQUfs/kFH7P3wBAAB8AQAABgAAAAAAAAA=
AQAAAFg6AACwT/s/sE/7P8wBAADMAQAABgAAAAAAAAA=
BAAAACQ8AAAAAAAAAAAAABQBAAAUAQAABgAAAAAAAAA=
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoGr7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfQ4NQCAIBgD9FABADRUAQP////8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgODYDgtPs/
AgAAACy1+z8gtfs/cOn6PwAAAAAAAAAABQAAAK3///8gAAAAIGv7PwEAAACAAAAA
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkKz7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1pIAQCAFBgD9FABADRUAQP////8XAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+TAIAwqvs/
rKr7PwAAAAAAAAAAwBsEAFcAAAA3AAAAnAH+PwAA9D8AAAAAAAAAAAAAAADDGwQA
AAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATID7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABogOQCAABgBsxABAd8QAQP////8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM44DYBQf/s/
AAAAAAAAAAABAAAAAQAAgAMAAAAjAAYA1JcIgEB/+z8DAAAAIwgGACAIBgABAAAA
IAgGAOCO+z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsHj7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABogOQCAFBgBsxABAd8QAQP////8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM44DYCwd/s/
AAAAAAMAAAABAAAAAQAAgAMAAAAjAAYAepEIgJB3+z8Ucfs/SB0AQCAEBgABAAAA
IAQGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFGn7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASBQIQCAFBgD9FABADRUAQPn///8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALqbCIAgaPs/
AAAAAFojBADUlwiAAFf7PwMAAAAjCAYASBQIgABo+z/cAPA/AQAAADgA+z8BAAAA
IAUGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmGz7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASBQIQCADBgD9FABADRUAQPj///8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALqbCIBAvfs/
AAAAAFojBADtWA2A4Kn7PwAIAAAEAPs/SBQIgCC9+z/cAPA/AQAAADgA+z8BAAAA
IAMGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9Ir7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASBQIQCAABgAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF2pCIDgifs/
AAAAAAAAAAAsgvs/AAAAAAAAAABgXPs/SBQIgMCJ+z/cAPA/AQAAADgA+z9wXPs/
iCsNQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANPv6PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASBQIQCAGBgAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJCNCIAg+vo/
AAAAAAAAAADoQfs/HQAAAFUAAADgUPs/SBQIgAD6+j/cAPA/AQAAADgA+z8BAAAA
IAYGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcFj7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkI0IQCAIBgAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMdCIBwV/s/
9FP7PwAAAABIVPs/AAAAAAEAAAAAAAAAkI0IgFBX+z8BAAAABAAAAOxB+z8KAAAA
AACAABwA9D8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
CAAAAEwCAAABAAAA
Q09SRQAAAAA=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkFH7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASBQIQCAOBgAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJCNCIBwUPs/
AAAAAAAAAADoQfs/zc0AAAEAAAAAAAAASBQIgFBQ+z/cAPA/AQAAADgA+z8BAAAA
IAAGAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
kLT7P7C1+z9yHwQAiED7P4hA+z+gavs/gED7PxIAAABYbPs/WGz7P6Bq+z8AAAAA
BwAAAByu+z91bmFsaWduZWRfcHRyX3QAAQAAABi2+z8AAAAAIAwGAA8AAADOzs7O
BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
ZFNAP4EiDkAwDAYAXCIOgMCd+z8CAAAAvStAPwCe+z9k6fo/AAAAAAAAAAAFAAAA
rf///yAAAAD0VPs/AQAAAIAAAAABAAAAAAAAAAAAAAAdAAAABQAAAP0UAEANFQBA
/////wEAAACAAAAAYCAIQFgL+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEAAACAAAAAAQAAAAAAAABcIg6A8J37PwEAAABk6fo/
WCcNgPCd+z8KAAAAZ+n6PwCe+z9k6fo/AAAAAAAAAACkIg6AIJ77PwoAAAABAAAA
jFNAPx4AAAC8K0A/BAAAACAAAAAgAACACAAAAAEAAAC8gQiAUJ77PwAAAAAAAAAA
vIEIgFCe+z8AAAAAAwAAAEAE+z8gAACAIQAGAAEAAAAAAAAAcJ77P4wiDkAAAAAA
IwAGAHRU+z8AAAAAAAAAAAAAAACQnvs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnJ77PwAAAAAAAAAAAAAAAAAAAAAAAAAA
776t3n0ODUAwCAYAWA4NgOC0+z8CAAAALLX7PyC1+z9w6fo/AAAAAAAAAAAFAAAA
rf///yAAAAAga/s/AQAAAIAAAAABAAAAAAAAAAAAAAAdAAAABQAAAP0UAEANFQBA
/////wEAAACAAAAAOCQIQFRH+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAEAAACAAAAAAQAAAAAAAABYDg2AELX7PwEAAABw6fo/
ELgNgBC1+z8KAAAAAwAAACC1+z9w6fo/AAAAAAAAAACgDg2AQLX7PwoAAACUDPs/
lAJAPx4AAACCWUA/AwAAAACOCIBQZ/s/AQAAANzn68S8gQiAcLX7PwAAAAAAAAAA
vIEIgHC1+z8AAAAAAwAAACAAAAAAAACAIQAGAAEAAAAAAAAAkLX7P4gODUAAAAAA
IwAGAIhA+z+gavs/AAAAAAAAAACwtfs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvLX7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAA=
bJX7P1CS+z9Ylfs/
UJL7P/CU+z/5GQAAUC/7P1Av+z9slfs/SC/7PxQAAAA0//o/NP/6P2yV+z8AAAAA
BQAAAFx1+z91bml0eVRhc2sAzs7Ozs4AAAAAAFiV+z8AAAAAIQAGAAwAAADOzs7O
BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
cKn7PxCs+z+PGwQAYED7P2BA+z+QrPs/WED7PxQAAAC0U/s/tFP7P5Cs+z8AAAAA
BQAAAICM+z91bml0eVRhc2sAzs7Ozs4AAAAAAHys+z8AAAAAIQAGAAwAAADOzs7O
BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQOaSAEAwCQYAD5MAgBCT+z+Mk/s/AAAAADwu+z8KAAAAVwAAADcAAAD0PwAA
AAD0PwDAAOAAAAAAPC77P8zMzAwAAAAABAAAABMAAAAQk/s/jJP7P/0UAEANFQBA
/////8QiCEDMzMwMHI4IQLgB+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAP//P7MAAAAAAAAAAAAAAAC/Bw6AMJP7P4yT+z//AAAA
uAH7PwAAAAAAAAAAAAAAAAIMDoBQk/s/jJP7P/8AAAB1bATAAP8AAAAA/wAAAAD/
PwMOgICT+z8BAAAAkJT7Pz8DDoCAk/s/AQAAANbEGZb+AAAAjJT7PwAAAAAQAAAA
vIEIgLCU+z8AAAAAAAAAAKWlpaWlpaWlpaWlpQAAAAAAAAAAAAAAAAAAAAAAAAAA
nCQIQNaSAEAwBQYAD5MAgDCq+z+sqvs/AAAAAAAAAADAGwQAVwAAADcAAACcAf4/
AAD0PwAAAAAAAAAAAAAAAMMbBAAAAAAABAAAABcAAAD//wAAAAAAAP0UAEANFQBA
/////5wmCEDDGwQA/IMIQLQ9+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAP//P7MAAAAAAAAAAAAAAAAzYQ2AUKr7P6yq+z//AAAA
tD37PwAAAAAAAAAAAAAAADZdDYBwqvs/rKr7P/8AAABVVQTAAP8AAAAA/wAAAAD/
o2ENgKCq+z8BAAAAsKv7P6NhDYCgqvs/AQAAANzn68T+AAAArKv7P////38QAAAA
vIEIgNCr+z8AAAAAAAAAAKWlpaWlpaWlpaWlpQAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADWxBmW
jJP7PyAAAIAhAAYA4En7PwAAAADQlPs/NAMOQAAAAAAjAAYAbJX7PwAAAAAAAAAA
AAAAAPCU+z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAD8lPs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADc5+vE
rKr7PwAAAIAhAAYAIGH7PwAAAADwq/s/mGENQAAAAAAjAAYAYED7P5Cs+z8AAAAA
AAAAABCs+z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAcrPs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAA=
DGn7P1Bn+z/4aPs/
UGf7P5Bo+z/Ozs7O7C77P3hh+z8Mafs/5C77PxkAAADOzs7Ozs7Ozgxp+z8AAAAA
AAAAAPxi+z9JRExFMQDOzs7Ozs7Ozs4AAQAAAPho+z8AAAAAIQAGAAcAAADOzs7O
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAA
kH77P9B/+z/Ozs7O/D/7P7h4+z9MgPs/9D/7PxkAAADOzs7Ozs7OzkyA+z8AAAAA
AAAAADx6+z9JRExFMQDOzs7Ozs7Ozs4AAQAAADiA+z8AAAAAIQAGAAcAAADOzs7O
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQCJrDkAwBAYAAhENgBBo+z8AAAAAAQAAgAAAAAABAAAAAwAAACMABgCZcwiA
AGj7PwMAAAAjCAYAIAgGAAEAAAAgCAYAwHf7PwAAAAClpaWlpaWlpWzEAEB3xABA
/////8QiCEABAAAAHI4IQFjV+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAACQeQhAAAAAAAAAAACZeQiAMGj7PwgAAAABAAAA
AQAAAAEAAAADAAAAIwAGALyBCIBQaPs/AAAAAAAAAAABAAAAIAAAgCEABgAAAAAA
AAAAAHBo+z+QeQhAAAAAACMABgBwYfs/AAAAAAEAAAAAAAAAkGj7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJxo+z8AAAAA
nCQIQAaIDkAwAAYAzjgNgFB/+z8AAAAAAAAAAAEAAAABAACAAwAAACMABgDUlwiA
QH/7PwMAAAAjCAYAIAgGAAEAAAAgCAYA4I77PwAAAAD//wAAAAAAAGzEAEB3xABA
/////5wmCEABAAAA/IMIQHQR+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAADcnQhAAAAAAAAAAADlnQiAcH/7PwgAAAABAAAA
AAAAAAAAAAAAAAAAAAAAALyBCICQf/s/AAAAAAAAAAABAAAAAAAAgCEABgAAAAAA
AAAAALB/+z/cnQhAAAAAACMABgD8P/s/sHj7PwAAAAAAAAAA0H/7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANx/+z8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
cGH7P7Bf+z9cYfs/
sF/7P/Bg+z/Ozs7OFGn7P+wu+z9wYfs/5C77PxkAAADOzs7Ozs7OznBh+z8AAAAA
AAAAAGBb+z9JRExFMADOzs7Ozs7Ozs4AAAAAAFxh+z8AAAAAIQAGAAYAAADOzs7O
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
8Hb7PzB4+z/Ozs7OVID7P/w/+z+wePs/9D/7PxkAAADOzs7Ozs7OzrB4+z8AAAAA
AAAAAKBy+z9JRExFMADOzs7Ozs7Ozs4AAAAAAJx4+z8AAAAAIQAGAAYAAADOzs7O
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQCJrDkAwBwYAAhENgHBg+z8AAAAAAwAAAAAAAAABAAAAAwAAACMBBgAjAAYA
DGn7PwAAAAABAAAA2IMIgJCO+z8AAAAAYFv7PwAAAACILfs/AAAAAGzEAEB3xABA
/////8QiCECQjvs/HI4IQLjN+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAP//P7MAAAAAAAAAAAAAAACZeQiAkGD7PwgAAAAAAAAA
AAAAAAEAAAADAAAAIwEGALyBCICwYPs/AAAAAAAAAAABAAAAIAAAgCEABgAAAAAA
AAAAANBg+z+QeQhAAAAAACMABgAMafs/AAAAAAAAAAAAAAAA8GD7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPxg+z8AAAAA
nCQIQAaIDkAwBQYAzjgNgLB3+z8AAAAAAwAAAAEAAAABAACAAwAAACMABgB6kQiA
kHf7PxRx+z9IHQBAIAQGAAEAAAAgBAYAAAAAAAAAAAD//wAAAAAAAGzEAEB3xABA
/////5wmCEABAAAA/IMIQNQJ+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAADcnQhAAAAAAAAAAADlnQiA0Hf7PwgAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAALyBCIDwd/s/AAAAAAAAAAABAAAAAAAAgAAAAAAAAAAA
AAAAABB4+z/cnQhAAAAAACMABgD8P/s/sHj7PwEAAAAAAAAAMHj7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADx4+z8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
6FL7P1BR+z/UUvs/
UFH7P3BS+z/EIQAA2C77P3RW+z/oUvs/0C77PxQAAAAsVvs/LFb7P+hS+z8AAAAA
BQAAANhK+z9iYWRfcHRyX3Rhc2sAzs4A////f9RS+z8AAAAAIQAGAA4AAADOzs7O
BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
YGf7P6Bo+z9aIwQA6D/7P6Bs+z8Uafs/4D/7PxQAAADOzs7Ozs7OzhRp+z8AAAAA
BQAAAARh+z9iYWRfcHRyX3Rhc2sAzs4A////fwBp+z8AAAAAIQAGAA4AAADOzs7O
BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQIJ3CEAwBwYAJyIOgBBS+z/EIQAAAAAAAEAE+z8gAACAIQAGACMIBgCCdwiA
8FH7PwAAAADEIQAA7BwIgDA/+z/cAPA/AQAAAAAAAABYJw2A0FH7P/0UAEANFQBA
+f///8QiCEAwP/s/HI4IQDi/+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAOwcCIAwP/s/3ADwPwEAAAC8gQiAMFL7PwAAAAAAAAAA
QAT7PyAAAIAhAAYAIwgGAAAAAABQUvs/GCIOQAAAAAAjAAYAbJX7PwAAAAAAAAAA
AAAAAHBS+z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAB8Uvs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAA==
bFb7P4Cl+z8Ep/s/
gKX7P6Cm+z/EIQAA8FL7P9gu+z9sVvs/0C77Pw8AAADOzs7Ozs7OzmxW+z8AAAAA
CgAAAAif+z9mYWlsZWRfYXNzZXJ0X3QAAAAAAASn+z8AAAAAIQAGABAAAADOzs7O
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQIJ3CEAwCQYAayEOgECm+z/EIQAAAAAAAEAE+z8gAACAIQAGAAAAAACCdwiA
IKb7PwAAAADEIQAAeAYOgMCS+z8ACAAAQBb7PwAAAABYJw2AAKb7P/0UAEANFQBA
+P///8QiCEDAkvs/HI4IQGgT+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAHgGDoDAkvs/AAgAAEAW+z+8gQiAYKb7PwAAAAAAAAAA
QAT7PyAAAIAhAAYAAAAAAAAAAACApvs/XCEOQAAAAAAjAAYAbFb7PwAAAAAAAAAA
AAAAAKCm+z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAACspvs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAA==
tHP7PwBy+z+gc/s/
AHL7P0Bz+z8AAAAAxC77P8Qu+z+0c/s/vC77PxgAAADEavs/xGr7P7Rz+z+8avs/
AQAAAKRr+z9UbXIgU3ZjAM7Ozs7Ozs4AAAAAAKBz+z8AAAAAIQAGAAgAAADOzs7O
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQPKTCEAwCgYAJ5UIgMBy+z8wMfs/AAAAAAEAAAAgAACAIQAGAAAAAADykwiA
oHL7PwAAAAA8Lvs/7Gr7PwAAAAAAAAAAIwAGAAAAAAClpaWlpaWlpQAAAAAAAAAA
AAAAAMQiCEAAAAAAHI4IQAjg+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAMlQhAAAAAAAAAAAC8gQiA8HL7PwAAAAAAAAAA
AAAAAAAAAAAAAAAA1sQZlgAAAAAAAAAAAAAAAAAAAAAAAAAAIHP7PwyVCEAAAAAA
COD6PwAAAAABAAAA1sQZliMABgDUWfs/AAAAAAEAAAAAAAAAQHP7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAExz+z8AAAAA
nCQIQEgUCEAwBQYAupsIgCBo+z8AAAAAWiMEANSXCIAAV/s/AwAAACMIBgBIFAiA
AGj7P9wA8D8BAAAAOAD7PwEAAAAgBQYAAAAAAAAAAAD//wAAAAAAAP0UAEANFQBA
+f///5wmCEABAAAA/IMIQET6+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAADACQD8YAAAAgllAPwEAAAAbDg2AQGj7P1ojBACUDPs/
1JcIgABX+z8DAAAAIwgGALyBCIBgaPs/AAAAAAAAAAAgAAAAAAAAgCEABgAAAAAA
AAAAAIBo+z8MDg1AAAAAACMABgBgQPs/kKz7PwAAAAAAAAAAoGj7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKxo+z8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
lPv6P+D5+j+A+/o/
4Pn6PyD7+j/Ozs7OzED7P8Q6+z+U+/o/YC77PwMAAADY6vo/2Or6P5T7+j/Q6vo/
FgAAAITr+j9lc3BfdGltZXIAzs7Ozs4AAAAAAID7+j8AAAAAIQAGAAEAAADOzs7O
FgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
gLz7P8C9+z9aIwQAHGn7P+g/+z+YbPs/4D/7Pw8AAAC8avs/WGz7P5hs+z8AAAAA
CgAAACy2+z9mYWlsZWRfYXNzZXJ0X3QAAAAAACi++z8AAAAAIQAGABAAAADOzs7O
CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQLSLCEAwAAYAmw8NgKD6+j+s6vo/AAAAAADr+j8AAAAAAQAAAAAAAAC0iwiA
gPr6PwAAAADYMPs/2DD7P1A5+z8DAAAAIw4GAAAAAAClpaWlpaWlpQAAAAAAAAAA
AAAAAMQiCEBQOfs/HI4IQOhn+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAACIDw1AAAAAAAAAAAC8gQiA4Pr6PwAAAAAAAAAA
AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAANbEGZYAAAAAAAAAAAAAAAAAAAAA
AAAAAAD7+j+IDw1AAAAAACMABgCU+/o/AAAAAAEAAAAAAAAAIPv6PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACz7+j8AAAAA
nCQIQEgUCEAwAwYAupsIgEC9+z8AAAAAWiMEAO1YDYDgqfs/AAgAAAQA+z9IFAiA
IL37P9wA8D8BAAAAOAD7PwEAAAAgAwYAAAAAAAAAAAD//wAAAAAAAP0UAEANFQBA
+P///5wmCEABAAAA/IMIQGRP+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAEQBQD8eAAAAgllAPwEAAABbDQ2AYL37P1ojBACUDPs/
7VgNgOCp+z8ACAAABAD7P7yBCICAvfs/AAAAAAAAAAAgAAAAAAAAgCEABgAAAAAA
AAAAAKC9+z9MDQ1AAAAAACMABgDEQPs/mGz7PwAAAAAAAAAAwL37PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMy9+z8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
xED7P/A++z+wQPs/
8D77P1BA+z/Ozs7OaC77P5z7+j/EQPs/YC77PwEAAAB0PPs/dDz7P8RA+z9sPPs/
GAAAALQ8+z9pcGMxAM7Ozs7Ozs7Ozs4AAQAAALBA+z8AAAAAIQAGAAMAAADOzs7O
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
IIn7P4CK+z8AAAAA1D/7P9Q/+z/0ivs/zD/7PxgAAAAEgvs/BIL7P/SK+z/8gfs/
AQAAAOSC+z9UbXIgU3ZjAM7Ozs7Ozs4AAAAAAOCK+z8AAAAAIQAGAAgAAADOzs7O
AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQOwcCEAwCAYAtIsIgLA/+z8BAAAA2DD7P9ww+z8KAAAAAACAABwA9D/sHAiA
kD/7P+AA8D8BAAAAKAD7PwEAAAAgCAYAwDz7PwAAAACwP/s/AQAAAAAAAAAAAAAA
AAAAAMQiCEABAAAAHI4IQBit+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAP//P7MAAAAAAAAAAAAAAAA3HwiA0D/7P0g8+z8AAAAA
3DD7PwoAAAAAAIAAHAD0P7yBCIAQQPs/AQAAAMw1CEB0lfs/CgAAAAAAgAD/////
vIEIgAAAAAD0GQAA1sQZlpw8+z8AAAAAAQAAAAAAAAAAAAAAMED7PwgfCEABAAAA
AQAAAMRA+z8AAAAAAAAAAAAAAABQQPs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
RBAIgIB9/j8oAAAAKAAAAAAAAAAAAAAAXED7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
nCQIQEgUCEAwAAYAXakIgOCJ+z8AAAAAAAAAACyC+z8AAAAAAAAAAGBc+z9IFAiA
wIn7P9wA8D8BAAAAOAD7P3Bc+z+IKw1AAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA
AAAAAJwmCEBwXPs//IMIQCQc+z8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAJwkCEC0gQhAMAAFANzn68SPqgiAAIr7P/RB+z8AAAAA
AAAAAHSqCEAAAAAAAAAAALyBCIAwivs/AAAAAAAAAAAAAAAAAAAAAAAAAADc5+vE
AQAAAAAAAIAhAAYAIwAGAAAAAABgivs/dKoIQAAAAAAkHPs/AAAAAAEAAADc5+vE
IwAGABBA+z8Ucfs/AAAAAAAAAACAivs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjIr7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA==
vDr7PwA5+z+oOvs/
ADn7P0A6+z/Ozs7OnPv6P2gu+z+8Ovs/YC77PwEAAACg//o/oP/6P7w6+z+Y//o/
GAAAAKw2+z9pcGMwAM7Ozs7Ozs7Ozs4AAAAAAKg6+z8AAAAAIQAGAAIAAADOzs7O
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/Oj6P2Tp+j/M6fo/
AAAAAAAAAAABAAAAAAAAAGg6QD8AAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
YPn6P8D6+j/Ozs7OeFj7P5hR+z80+/o/cD/7PwMAAADk6vo/5Or6PzT7+j/c6vo/
FgAAACTr+j9lc3BfdGltZXIAzs7Ozs4AAAAAACD7+j8AAAAAIQAGAAEAAADOzs7O
FgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
xCAIQLSLCEAwDgYANx8IgMA5+z90//o/AAAAAMj/+j8AAAAAAQAAAAIAAAC0iwiA
oDn7PwAAAADYMPs/2DD7P83NAAABAAAAAAAAAAAAAAClpaWlpaWlpQAAAAAAAAAA
AAAAAMQiCEDNzQAAHI4IQAin+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAIHwhAAAAAAAAAAAC8gQiAADr7PwAAAAAAAAAA
AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAANbEGZYAAAAAAAAAAAAAAAAAAAAA
AAAAACA6+z8IHwhAAAAAACMDBgC8Ovs/AQAAAAEAAAAAAAAAQDr7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAL4PCICAO/4/SC77P9bEGZYAAAAAAAAAAEw6+z8AAAAA
nCQIQEgUCEAwBgYAkI0IgCD6+j8AAAAAAAAAAOhB+z8dAAAAVQAAAOBQ+z9IFAiA
APr6P9wA8D8BAAAAOAD7PwEAAAAgBgYAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAA
AAAAAJwmCEABAAAA/IMIQGSM+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAJwkCEC0gQhAMAAFAAAAAAAHOA2AQPr6P7jq+j8AAAAA
AAAAAPQ3DUAAAAAAAAAAALyBCICA+vo/AAAAAAAAAAAAAAAAAAAAAAAAAAD/////
AAAAAAAAAADz1gMA3OfrxAzr+j8AAAAAAQAAACMOBgAAAAAAoPr6P/Q3DUAAAAAA
IwAGALRB+z80+/o/AAAAAAAAAADA+vo/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzPr6PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
AAAAAAAAAAAAAAAAAAAAAA==
sFb7P/BX+z/Ozs7OeD/7Pzz7+j9wWPs/cD/7PwEAAAAgVPs/IFT7P3BY+z8YVPs/
GAAAAGBU+z9pcGMxAM7Ozs7Ozs7Ozs4AAQAAAFxY+z8AAAAAIQAGAAMAAADOzs7O
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
nCQIQJCNCEAwCAYAQx0IgHBX+z/0U/s/AAAAAEhU+z8AAAAAAQAAAAAAAACQjQiA
UFf7PwEAAAAEAAAA7EH7PwoAAAAAAIAAHAD0PwAAAAD//wAAAAAAAAAAAAAAAAAA
AAAAAJwmCEAKAAAA/IMIQJTp+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAJis+z8KAAAAAACAABwA9D+8gQiAsFf7PwEAAAAAAAAA
mKz7PwoAAAAAAIAA/////7yBCIAAAAAAiRsEANzn68RIVPs/AAAAAAEAAAAAAAAA
AAAAANBX+z8QHQhAAQAAAAEAAADcQfs/cFj7PwAAAAAAAAAA8Ff7PwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAOARCICAff4/KAAAACgAAAAAAAAAAAAAAPxX+z8AAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
sE/7PxBR+z/Ozs7OPPv6P3g/+z+QUfs/cD/7PwEAAABATfs/QE37P5BR+z84Tfs/
GAAAAIBN+z9pcGMwAM7Ozs7Ozs7Ozs4AAAAAAHxR+z8AAAAAIQAGAAIAAADOzs7O
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACOn6P3Dp+j/Y6fo/
AAAAAAAAAAABAAAAAAAAAAAAAAAAAAAASB0AQAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADOzs4=
nCQIQEgUCEAwDgYAkI0IgHBQ+z8AAAAAAAAAAOhB+z/NzQAAAQAAAAAAAABIFAiA
UFD7P9wA8D8BAAAAOAD7PwEAAAAgAAYAAQAAAAAAAAD//wAAAAAAAAAAAAAAAAAA
AAAAAJwmCEABAAAA/IMIQLTi+j8AAAAAAAAAAAAAAAD//z+zAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAJwkCEC0gQhAMAAFAAAAAABDHQiAkFD7PxRN+z8AAAAA
AAAAABAdCEAAAAAAAAAAALyBCIDQUPs/AAAAAAAAAAAAAAAAAAAAAAAAAAD/////
AAAAAAAAAAAAAAAA3OfrxGhN+z8AAAAAAQAAAAIAAAAAAAAA8FD7PxAdCEAAAAAA
IwMGANxB+z+QUfs/AQAAAAAAAAAQUfs/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
WhEIgFA7/j9YP/s/3OfrxAAAAAAAAAAAHFH7PwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
FAAAAEgAAABKIAAA
RVNQX0NPUkVfRFVNUF9JTkZPAAA=
AgAAADJhYTkyNjY1YTFiNzg5OWMyZjI4NjhmOGRhZWRmZDVhMWUzNWExYWVhMzY1
ZjkzNmRjODllZThjYzcxNzhhNTMAAAAA
DAAAAJQAAAClAgAA
RVhUUkFfSU5GTwAA
oGr7P+gAAAAdAAAA7gAAAAUAAADCAAAAAAAAAMMAAAAAAAAAxAAAAAAAAADFAAAA
AAAAAMYAAAAAAAAAxwAAAAAAAACxAAAAm4cOQLIAAAAAAAAAswAAAAAAAAC0AAAA
AAAAALUAAAAAAAAAtgAAAAAAAAC3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAA==
v1VmGg==

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -18,16 +18,31 @@ Configuration
There are a number of core dump related configuration options which user can choose in project configuration menu (`idf.py menuconfig`).
1. Core dump data destination (`Components -> ESP32-specific config -> Core dump -> Data destination`):
1. Core dump data destination (`Components -> Core dump -> Data destination`):
* Disable core dump generation
* Save core dump to flash
* Print core dump to UART
* Save core dump to Flash (Flash)
* Print core dump to UART (UART)
* Disable core dump generation (None)
2. Maximum number of tasks snapshots in core dump (`Components -> ESP32-specific config -> Core dump -> Maximum number of tasks`).
2. Core dump data format (`Components -> Core dump -> Core dump data format`):
3. Delay before core dump is printed to UART (`Components -> ESP32-specific config -> Core dump -> Delay before print to UART`). Value is in ms.
* ELF format (Executable and Linkable Format file for core dump)
* Binary format (Basic binary format for core dump)
The ELF format contains extended features and allow to save more information about broken tasks and crashed software but it requires more space in the flash memory.
It also stores SHA256 of crashed application image. This format of core dump is recommended for new software designs and is flexible enough to extend saved information for future revisions.
The Binary format is kept for compatibility standpoint, it uses less space in the memory to keep data and provides better performance.
3. Maximum number of tasks snapshots in core dump (`Components -> Core dump -> Maximum number of tasks`).
4. Delay before core dump is printed to UART (`Components -> Core dump -> Delay before print to UART`). Value is in ms.
5. Type of data integrity check for core dump (`Components -> Core dump -> Core dump data integrity check`).
* Use CRC32 for core dump integrity verification
* Use SHA256 for core dump integrity verification
The SHA256 hash algorithm provides greater probability of detecting corruption than a CRC32 with multiple bit errors. The CRC32 option provides better calculation performance and consumes less memory for storage.
Save core dump to flash
-----------------------
@ -75,7 +90,7 @@ To overcome this issue you can use ROM ELF provided by Espressif (https://dl.esp
Running 'espcoredump.py'
------------------------------------
------------------------
Generic command syntax: