mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-05 05:34:32 +02:00
espsystem: Rearchitecture and fix eh_frame_parser bugs
eh_frame_parser is architecture independent, thus the files have been rearchitectured. Some bugs have been fixed in the test. A README file has also been added to eh_frame_parser host test directory. eh_frame_parser is now able to detect empty gaps in .eh_frame_hdr table (missing DWARF information). Fix a bug occuring when parsing backtraces originated from abort(). Fix build missing dependencies issue.
This commit is contained in:
@@ -355,3 +355,10 @@ test_log:
|
|||||||
- cd ${IDF_PATH}/components/log/host_test/log_test
|
- cd ${IDF_PATH}/components/log/host_test/log_test
|
||||||
- idf.py build
|
- idf.py build
|
||||||
- build/test_log_host.elf
|
- build/test_log_host.elf
|
||||||
|
|
||||||
|
test_eh_frame_parser:
|
||||||
|
extends: .host_test_template
|
||||||
|
script:
|
||||||
|
- cd ${IDF_PATH}/components/esp_system/test_eh_frame_parser
|
||||||
|
- make
|
||||||
|
- ./eh_frame_test
|
||||||
|
@@ -15,6 +15,7 @@ unset(link_options)
|
|||||||
|
|
||||||
# Add the following build specifications here, since these seem to be dependent
|
# Add the following build specifications here, since these seem to be dependent
|
||||||
# on config values on the root Kconfig.
|
# on config values on the root Kconfig.
|
||||||
|
|
||||||
if(NOT BOOTLOADER_BUILD)
|
if(NOT BOOTLOADER_BUILD)
|
||||||
|
|
||||||
if(CONFIG_COMPILER_OPTIMIZATION_SIZE)
|
if(CONFIG_COMPILER_OPTIMIZATION_SIZE)
|
||||||
|
@@ -118,6 +118,6 @@ REGION_ALIAS("rtc_data_location", rtc_iram_seg );
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||||
ASSERT ((_eh_frame_end > _eh_frame), "Error: eh_frame size is null!");
|
ASSERT ((__eh_frame_end > __eh_frame), "Error: eh_frame size is null!");
|
||||||
ASSERT ((_eh_frame_hdr_end > _eh_frame_hdr), "Error: eh_frame_hdr size is null!");
|
ASSERT ((__eh_frame_hdr_end > __eh_frame_hdr), "Error: eh_frame_hdr size is null!");
|
||||||
#endif
|
#endif
|
||||||
|
@@ -339,22 +339,28 @@ SECTIONS
|
|||||||
*(.srodata.*)
|
*(.srodata.*)
|
||||||
_thread_local_end = ABSOLUTE(.);
|
_thread_local_end = ABSOLUTE(.);
|
||||||
_rodata_reserved_end = ABSOLUTE(.);
|
_rodata_reserved_end = ABSOLUTE(.);
|
||||||
. = ALIGN(4);
|
. = ALIGN(ALIGNOF(.eh_frame));
|
||||||
} > default_rodata_seg
|
} > default_rodata_seg
|
||||||
|
|
||||||
.eh_frame :
|
/* Keep this section shall be at least aligned on 4 */
|
||||||
|
.eh_frame : ALIGN(8)
|
||||||
{
|
{
|
||||||
_eh_frame = ABSOLUTE(.);
|
__eh_frame = ABSOLUTE(.);
|
||||||
KEEP (*(.eh_frame))
|
KEEP (*(.eh_frame))
|
||||||
_eh_frame_end = ABSOLUTE(.);
|
__eh_frame_end = ABSOLUTE(.);
|
||||||
} > drom0_0_seg
|
/* Guarantee that this section and the next one will be merged by making
|
||||||
|
* them adjacent. */
|
||||||
|
. = ALIGN(ALIGNOF(.eh_frame_hdr));
|
||||||
|
} > default_rodata_seg
|
||||||
|
|
||||||
.eh_frame_hdr :
|
/* To avoid any exception in C++ exception frame unwinding code, this section
|
||||||
|
* shall be aligned on 8. */
|
||||||
|
.eh_frame_hdr : ALIGN(8)
|
||||||
{
|
{
|
||||||
_eh_frame_hdr = ABSOLUTE(.);
|
__eh_frame_hdr = ABSOLUTE(.);
|
||||||
KEEP (*(.eh_frame_hdr))
|
KEEP (*(.eh_frame_hdr))
|
||||||
_eh_frame_hdr_end = ABSOLUTE(.);
|
__eh_frame_hdr_end = ABSOLUTE(.);
|
||||||
} > drom0_0_seg
|
} > default_rodata_seg
|
||||||
|
|
||||||
.flash.rodata_noload (NOLOAD) :
|
.flash.rodata_noload (NOLOAD) :
|
||||||
{
|
{
|
||||||
|
@@ -116,3 +116,8 @@ REGION_ALIAS("rtc_data_location", rtc_iram_seg );
|
|||||||
ASSERT(_flash_rodata_dummy_start == ORIGIN(default_rodata_seg),
|
ASSERT(_flash_rodata_dummy_start == ORIGIN(default_rodata_seg),
|
||||||
".flash_rodata_dummy section must be placed at the beginning of the rodata segment.")
|
".flash_rodata_dummy section must be placed at the beginning of the rodata segment.")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||||
|
ASSERT ((__eh_frame_end > __eh_frame), "Error: eh_frame size is null!");
|
||||||
|
ASSERT ((__eh_frame_hdr_end > __eh_frame_hdr), "Error: eh_frame_hdr size is null!");
|
||||||
|
#endif
|
||||||
|
@@ -289,9 +289,6 @@ SECTIONS
|
|||||||
*(.gcc_except_table .gcc_except_table.*)
|
*(.gcc_except_table .gcc_except_table.*)
|
||||||
*(.gnu.linkonce.e.*)
|
*(.gnu.linkonce.e.*)
|
||||||
*(.gnu.version_r)
|
*(.gnu.version_r)
|
||||||
. = (. + 3) & ~ 3;
|
|
||||||
__eh_frame = ABSOLUTE(.);
|
|
||||||
KEEP(*(.eh_frame))
|
|
||||||
. = (. + 7) & ~ 3;
|
. = (. + 7) & ~ 3;
|
||||||
/*
|
/*
|
||||||
* C++ constructor and destructor tables
|
* C++ constructor and destructor tables
|
||||||
@@ -342,7 +339,27 @@ SECTIONS
|
|||||||
*(.srodata.*)
|
*(.srodata.*)
|
||||||
_thread_local_end = ABSOLUTE(.);
|
_thread_local_end = ABSOLUTE(.);
|
||||||
_rodata_reserved_end = ABSOLUTE(.);
|
_rodata_reserved_end = ABSOLUTE(.);
|
||||||
. = ALIGN(4);
|
. = ALIGN(ALIGNOF(.eh_frame));
|
||||||
|
} > default_rodata_seg
|
||||||
|
|
||||||
|
/* Keep this section shall be at least aligned on 4 */
|
||||||
|
.eh_frame : ALIGN(4)
|
||||||
|
{
|
||||||
|
__eh_frame = ABSOLUTE(.);
|
||||||
|
KEEP (*(.eh_frame))
|
||||||
|
__eh_frame_end = ABSOLUTE(.);
|
||||||
|
/* Guarantee that this section and the next one will be merged by making
|
||||||
|
* them adjacent. */
|
||||||
|
. = ALIGN(ALIGNOF(.eh_frame_hdr));
|
||||||
|
} > default_rodata_seg
|
||||||
|
|
||||||
|
/* To avoid any exception in C++ exception frame unwinding code, this section
|
||||||
|
* shall be aligned on 8. */
|
||||||
|
.eh_frame_hdr : ALIGN(8)
|
||||||
|
{
|
||||||
|
__eh_frame_hdr = ABSOLUTE(.);
|
||||||
|
KEEP (*(.eh_frame_hdr))
|
||||||
|
__eh_frame_hdr_end = ABSOLUTE(.);
|
||||||
} > default_rodata_seg
|
} > default_rodata_seg
|
||||||
|
|
||||||
/* Marks the end of IRAM code segment */
|
/* Marks the end of IRAM code segment */
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
idf_build_get_property(target IDF_TARGET)
|
idf_build_get_property(target IDF_TARGET)
|
||||||
|
|
||||||
|
set(priv_requires efuse)
|
||||||
set(requires soc)
|
set(requires soc)
|
||||||
if(${target} STREQUAL "esp32")
|
if(${target} STREQUAL "esp32")
|
||||||
list(APPEND requires efuse)
|
list(APPEND requires efuse)
|
||||||
@@ -17,7 +18,10 @@ if(NOT BOOTLOADER_BUILD)
|
|||||||
"mac_addr.c"
|
"mac_addr.c"
|
||||||
"sleep_modes.c"
|
"sleep_modes.c"
|
||||||
"regi2c_ctrl.c")
|
"regi2c_ctrl.c")
|
||||||
list(APPEND priv_requires esp_ipc)
|
list(APPEND priv_requires esp_ipc)
|
||||||
|
else()
|
||||||
|
# Requires "_esp_error_check_failed()" function
|
||||||
|
list(APPEND priv_requires "esp_system")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
idf_component_register(SRCS ${srcs}
|
idf_component_register(SRCS ${srcs}
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
idf_build_get_property(target IDF_TARGET)
|
idf_build_get_property(target IDF_TARGET)
|
||||||
|
|
||||||
set(srcs)
|
set(srcs "esp_err.c")
|
||||||
|
|
||||||
if(CONFIG_IDF_ENV_FPGA)
|
if(CONFIG_IDF_ENV_FPGA)
|
||||||
list(APPEND srcs "fpga_overrides.c")
|
list(APPEND srcs "fpga_overrides.c")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(BOOTLOADER_BUILD)
|
if(BOOTLOADER_BUILD)
|
||||||
# Bootloader relies on some Kconfig options defined in esp_system.
|
# "_esp_error_check_failed()" requires spi_flash module
|
||||||
idf_component_register(SRCS "${srcs}")
|
# Bootloader relies on some Kconfig options defined in esp_system.
|
||||||
|
idf_component_register(SRCS "${srcs}" REQUIRES spi_flash)
|
||||||
else()
|
else()
|
||||||
list(APPEND srcs "crosscore_int.c"
|
list(APPEND srcs "crosscore_int.c"
|
||||||
"esp_err.c"
|
"esp_err.c"
|
||||||
@@ -26,6 +27,10 @@ else()
|
|||||||
list(APPEND srcs "dbg_stubs.c")
|
list(APPEND srcs "dbg_stubs.c")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CONFIG_ESP_SYSTEM_USE_EH_FRAME)
|
||||||
|
list(APPEND srcs "eh_frame_parser.c")
|
||||||
|
endif()
|
||||||
|
|
||||||
idf_component_register(SRCS "${srcs}"
|
idf_component_register(SRCS "${srcs}"
|
||||||
INCLUDE_DIRS include
|
INCLUDE_DIRS include
|
||||||
PRIV_REQUIRES spi_flash
|
PRIV_REQUIRES spi_flash
|
||||||
|
@@ -98,10 +98,12 @@ menu "ESP System Settings"
|
|||||||
default n
|
default n
|
||||||
depends on IDF_TARGET_ARCH_RISCV
|
depends on IDF_TARGET_ARCH_RISCV
|
||||||
help
|
help
|
||||||
Generate DWARF information in the resulting binary to perform backtracing when panics occur. Activating
|
Generate DWARF information for each function of the project. These information will parsed and used to
|
||||||
this option will activate asynchronous frame unwinding and generation of both .eh_frame and .eh_frame_hdr
|
perform backtracing when panics occur. Activating this option will activate asynchronous frame unwinding
|
||||||
sections, resulting in a bigger binary size (20% to 100% larger). This option shall be not be used for
|
and generation of both .eh_frame and .eh_frame_hdr sections, resulting in a bigger binary size (20% to
|
||||||
production.
|
100% larger). The main purpose of this option is to be able to have a backtrace parsed and printed by
|
||||||
|
the program itself, regardless of the serial monitor used.
|
||||||
|
This option shall NOT be used for production.
|
||||||
|
|
||||||
menu "Memory protection"
|
menu "Memory protection"
|
||||||
|
|
||||||
|
@@ -25,13 +25,14 @@
|
|||||||
* http://dwarfstd.org/Download.php
|
* http://dwarfstd.org/Download.php
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "port/eh_frame_parser.h"
|
#include "eh_frame_parser.h"
|
||||||
#include "port/eh_frame_parser_impl.h"
|
|
||||||
#include "esp_private/panic_internal.h"
|
#include "esp_private/panic_internal.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||||
|
|
||||||
|
#include "eh_frame_parser_impl.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Dimension of an array (number of elements)
|
* @brief Dimension of an array (number of elements)
|
||||||
*/
|
*/
|
||||||
@@ -90,8 +91,8 @@
|
|||||||
/**
|
/**
|
||||||
* @brief Pointers to both .eh_frame_hdr and .eh_frame sections.
|
* @brief Pointers to both .eh_frame_hdr and .eh_frame sections.
|
||||||
*/
|
*/
|
||||||
#define EH_FRAME_HDR_ADDR (&_eh_frame_hdr)
|
#define EH_FRAME_HDR_ADDR (&__eh_frame_hdr)
|
||||||
#define EH_FRAME_ADDR (&_eh_frame)
|
#define EH_FRAME_ADDR (&__eh_frame)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Structure of .eh_frame_hdr section header.
|
* @brief Structure of .eh_frame_hdr section header.
|
||||||
@@ -259,8 +260,8 @@ typedef struct {
|
|||||||
* @brief Symbols defined by the linker.
|
* @brief Symbols defined by the linker.
|
||||||
* Retrieve the addresses of both .eh_frame_hdr and .eh_frame sections.
|
* Retrieve the addresses of both .eh_frame_hdr and .eh_frame sections.
|
||||||
*/
|
*/
|
||||||
extern char _eh_frame_hdr;
|
extern char __eh_frame_hdr;
|
||||||
extern char _eh_frame;
|
extern char __eh_frame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Decode multiple bytes encoded in LEB128.
|
* @brief Decode multiple bytes encoded in LEB128.
|
||||||
@@ -439,22 +440,22 @@ static const table_entry* esp_eh_frame_find_entry(const table_entry* sorted_tabl
|
|||||||
/* Signed comparisons. */
|
/* Signed comparisons. */
|
||||||
const int32_t sfun_addr = (int32_t) fun_addr;
|
const int32_t sfun_addr = (int32_t) fun_addr;
|
||||||
const int32_t snxt_addr = (int32_t) nxt_addr;
|
const int32_t snxt_addr = (int32_t) nxt_addr;
|
||||||
if (sfun_addr <= ra && snxt_addr > ra)
|
if (sfun_addr <= ra && snxt_addr > ra)
|
||||||
found = true;
|
found = true;
|
||||||
else if (snxt_addr <= ra)
|
else if (snxt_addr <= ra)
|
||||||
begin = middle + 1;
|
begin = middle + 1;
|
||||||
else
|
else
|
||||||
end = middle;
|
end = middle;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/* Unsigned comparisons. */
|
/* Unsigned comparisons. */
|
||||||
const uint32_t ura = (uint32_t) ra;
|
const uint32_t ura = (uint32_t) ra;
|
||||||
if (fun_addr <= ura && nxt_addr > ura)
|
if (fun_addr <= ura && nxt_addr > ura)
|
||||||
found = true;
|
found = true;
|
||||||
else if (nxt_addr <= ura)
|
else if (nxt_addr <= ura)
|
||||||
begin = middle + 1;
|
begin = middle + 1;
|
||||||
else
|
else
|
||||||
end = middle;
|
end = middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
middle = (end + begin) / 2;
|
middle = (end + begin) / 2;
|
||||||
@@ -799,10 +800,45 @@ static uint32_t esp_eh_frame_restore_caller_state(const uint32_t* fde,
|
|||||||
EXECUTION_FRAME_SP(*frame) = cfa_addr;
|
EXECUTION_FRAME_SP(*frame) = cfa_addr;
|
||||||
|
|
||||||
/* If the frame was not available, it would be possible to retrieve the return address
|
/* If the frame was not available, it would be possible to retrieve the return address
|
||||||
* register thanks to CIE structure. */
|
* register thanks to CIE structure.
|
||||||
return EXECUTION_FRAME_REG(frame, ra_reg);
|
* The return address points to the address the PC needs to jump to. It
|
||||||
|
* does NOT point to the instruction where the routine call occured.
|
||||||
|
* This can cause problems with functions without epilogue (i.e. function
|
||||||
|
* which last instruction is a function call). This happens when compiler
|
||||||
|
* optimization are ON or when a function is marked as "noreturn".
|
||||||
|
*
|
||||||
|
* Thus, in order to point to the call/jal instruction, we need to
|
||||||
|
* subtract at least 1 byte but not more than an instruction size.
|
||||||
|
*/
|
||||||
|
return EXECUTION_FRAME_REG(frame, ra_reg) - 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test whether the DWARF information for the given PC are missing or not.
|
||||||
|
*
|
||||||
|
* @param fde FDE associated to this PC. This FDE is the one found thanks to
|
||||||
|
* `esp_eh_frame_find_entry()`.
|
||||||
|
* @param pc PC to get information from.
|
||||||
|
*
|
||||||
|
* @return true is DWARF information are missing, false else.
|
||||||
|
*/
|
||||||
|
static bool esp_eh_frame_missing_info(const uint32_t* fde, uint32_t pc) {
|
||||||
|
if (fde == NULL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the range of this FDE entry. It is possible that there are some
|
||||||
|
* gaps between DWARF entries, in that case, the FDE entry found has
|
||||||
|
* indeed an initial_location very close to PC but doesn't reach it.
|
||||||
|
* For example, if FDE initial_location is 0x40300000 and its length is
|
||||||
|
* 0x100, but PC value is 0x40300200, then some DWARF information
|
||||||
|
* are missing as there is a gap.
|
||||||
|
* End the backtrace. */
|
||||||
|
const uint32_t initial_location = ((uint32_t) &fde[ESP_FDE_INITLOC_IDX] + fde[ESP_FDE_INITLOC_IDX]);
|
||||||
|
const uint32_t range_length = fde[ESP_FDE_RANGELEN_IDX];
|
||||||
|
|
||||||
|
return (initial_location + range_length) <= pc;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief When one step of the backtrace is generated, output it to the serial.
|
* @brief When one step of the backtrace is generated, output it to the serial.
|
||||||
@@ -824,10 +860,12 @@ void __attribute__((weak)) esp_eh_frame_generated_step(uint32_t pc, uint32_t sp)
|
|||||||
*
|
*
|
||||||
* @param frame_or Snapshot of the CPU registers when the CPU stopped its normal execution.
|
* @param frame_or Snapshot of the CPU registers when the CPU stopped its normal execution.
|
||||||
*/
|
*/
|
||||||
void esp_eh_frame_print_backtrace(const ExecutionFrame *frame_or)
|
void esp_eh_frame_print_backtrace(const void *frame_or)
|
||||||
{
|
{
|
||||||
|
assert(frame_or != NULL);
|
||||||
|
|
||||||
static dwarf_regs state = { 0 };
|
static dwarf_regs state = { 0 };
|
||||||
ExecutionFrame frame = *frame_or;
|
ExecutionFrame frame = *((ExecutionFrame*) frame_or);
|
||||||
uint32_t size = 0;
|
uint32_t size = 0;
|
||||||
uint8_t* enc_values = NULL;
|
uint8_t* enc_values = NULL;
|
||||||
bool end_of_backtrace = false;
|
bool end_of_backtrace = false;
|
||||||
@@ -865,16 +903,23 @@ void esp_eh_frame_print_backtrace(const ExecutionFrame *frame_or)
|
|||||||
const table_entry* from_fun = esp_eh_frame_find_entry(sorted_table, fde_count,
|
const table_entry* from_fun = esp_eh_frame_find_entry(sorted_table, fde_count,
|
||||||
table_enc, EXECUTION_FRAME_PC(frame));
|
table_enc, EXECUTION_FRAME_PC(frame));
|
||||||
|
|
||||||
if (from_fun == 0) {
|
/* Get absolute address of FDE entry describing the function where PC left of. */
|
||||||
|
uint32_t* fde = NULL;
|
||||||
|
|
||||||
|
if (from_fun != NULL) {
|
||||||
|
fde = esp_eh_frame_decode_address(&from_fun->fde_addr, table_enc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_eh_frame_missing_info(fde, EXECUTION_FRAME_PC(frame))) {
|
||||||
/* Address was not found in the list. */
|
/* Address was not found in the list. */
|
||||||
|
panic_print_str("\r\nBacktrace ended abruptly: cannot find DWARF information for"
|
||||||
|
" instruction at address 0x");
|
||||||
|
panic_print_hex(EXECUTION_FRAME_PC(frame));
|
||||||
|
panic_print_str("\r\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Get absolute address of FDE entry describing the function where PC left of. */
|
/* Clean and set the DWARF register structure. */
|
||||||
const uint32_t* fde = esp_eh_frame_decode_address(&from_fun->fde_addr, table_enc);
|
|
||||||
|
|
||||||
/* Clean and set the DWARF register structure.
|
|
||||||
* TODO: Initialization should be done by the instruction contained by the CIE associated to the FDE. */
|
|
||||||
memset(&state, 0, sizeof(dwarf_regs));
|
memset(&state, 0, sizeof(dwarf_regs));
|
||||||
|
|
||||||
const uint32_t prev_sp = EXECUTION_FRAME_SP(frame);
|
const uint32_t prev_sp = EXECUTION_FRAME_SP(frame);
|
@@ -15,13 +15,21 @@
|
|||||||
#ifndef EH_FRAME_PARSER_H
|
#ifndef EH_FRAME_PARSER_H
|
||||||
#define EH_FRAME_PARSER_H
|
#define EH_FRAME_PARSER_H
|
||||||
|
|
||||||
#include "eh_frame_parser_impl.h"
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Print backtrace for the given execution frame.
|
* @brief Print backtrace for the given execution frame.
|
||||||
*
|
*
|
||||||
* @param frame_or Snapshot of the CPU registers when the CPU stopped its normal execution.
|
* @param frame_or Snapshot of the CPU registers when the program stopped its
|
||||||
|
* normal execution. This frame is usually generated on the
|
||||||
|
* stack when an exception or an interrupt occurs.
|
||||||
*/
|
*/
|
||||||
void esp_eh_frame_print_backtrace(const ExecutionFrame *frame_or);
|
void esp_eh_frame_print_backtrace(const void *frame_or);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
@@ -1,4 +1,10 @@
|
|||||||
target_include_directories(${COMPONENT_LIB} PRIVATE . include PUBLIC soc)
|
set(INCLUDE_FILES "include" . include PUBLIC soc)
|
||||||
|
|
||||||
|
if(CONFIG_IDF_TARGET_ARCH_RISCV)
|
||||||
|
list(APPEND INCLUDE_FILES "include/riscv")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(${COMPONENT_LIB} PRIVATE ${INCLUDE_FILES})
|
||||||
target_include_directories(${COMPONENT_LIB} PUBLIC public_compat)
|
target_include_directories(${COMPONENT_LIB} PUBLIC public_compat)
|
||||||
|
|
||||||
set(srcs "cpu_start.c" "panic_handler.c" "brownout.c")
|
set(srcs "cpu_start.c" "panic_handler.c" "brownout.c")
|
||||||
|
@@ -14,6 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
|
||||||
#include "soc/extmem_reg.h"
|
#include "soc/extmem_reg.h"
|
||||||
#include "esp_private/panic_internal.h"
|
#include "esp_private/panic_internal.h"
|
||||||
#include "esp_private/panic_reason.h"
|
#include "esp_private/panic_reason.h"
|
||||||
@@ -29,7 +31,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||||
#include "port/eh_frame_parser.h"
|
#include "eh_frame_parser.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -324,11 +326,7 @@ void panic_arch_fill_info(void *frame, panic_info_t *info)
|
|||||||
info->frame = ®s;
|
info->frame = ®s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void panic_print_backtrace(const void *frame, int core)
|
static void panic_print_basic_backtrace(const void *frame, int core) {
|
||||||
{
|
|
||||||
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
|
||||||
esp_eh_frame_print_backtrace(frame);
|
|
||||||
#else
|
|
||||||
// Basic backtrace
|
// Basic backtrace
|
||||||
panic_print_str("\r\nStack memory:\r\n");
|
panic_print_str("\r\nStack memory:\r\n");
|
||||||
uint32_t sp = (uint32_t)((RvExcFrame *)frame)->sp;
|
uint32_t sp = (uint32_t)((RvExcFrame *)frame)->sp;
|
||||||
@@ -343,6 +341,20 @@ void panic_print_backtrace(const void *frame, int core)
|
|||||||
panic_print_str(y == per_line - 1 ? "\r\n" : " ");
|
panic_print_str(y == per_line - 1 ? "\r\n" : " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void panic_print_backtrace(const void *frame, int core)
|
||||||
|
{
|
||||||
|
#if CONFIG_ESP_SYSTEM_USE_EH_FRAME
|
||||||
|
if (!spi_flash_cache_enabled()) {
|
||||||
|
panic_print_str("\r\nWarning: SPI Flash cache is disabled, cannot process eh_frame parsing. "
|
||||||
|
"Falling back to basic backtrace.\r\n");
|
||||||
|
panic_print_basic_backtrace(frame, core);
|
||||||
|
} else {
|
||||||
|
esp_eh_frame_print_backtrace(frame);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
panic_print_basic_backtrace(frame, core);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
// Copyright 2020 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 EH_FRAME_PARSER_H
|
|
||||||
#define EH_FRAME_PARSER_H
|
|
||||||
|
|
||||||
#include "eh_frame_parser_impl.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Print backtrace for the given execution frame.
|
|
||||||
*
|
|
||||||
* @param frame_or Snapshot of the CPU registers when the CPU stopped its normal execution.
|
|
||||||
*/
|
|
||||||
void esp_eh_frame_print_backtrace(const ExecutionFrame *frame_or);
|
|
||||||
|
|
||||||
#endif
|
|
@@ -7,7 +7,6 @@ set(srcs "clk.c"
|
|||||||
"../../arch/riscv/expression_with_stack_asm.S"
|
"../../arch/riscv/expression_with_stack_asm.S"
|
||||||
"../../arch/riscv/panic_arch.c")
|
"../../arch/riscv/panic_arch.c")
|
||||||
|
|
||||||
set(srcs ${srcs} "../../arch/riscv/eh_frame_parser.c")
|
|
||||||
add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/" ${srcs})
|
add_prefix(srcs "${CMAKE_CURRENT_LIST_DIR}/" ${srcs})
|
||||||
|
|
||||||
target_sources(${COMPONENT_LIB} PRIVATE ${srcs})
|
target_sources(${COMPONENT_LIB} PRIVATE ${srcs})
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-W -fasynchronous-unwind-tables -I. -std=c99 -g -DCONFIG_ESP_SYSTEM_USE_EH_FRAME -m32
|
CFLAGS=-W -fasynchronous-unwind-tables -I. -I../include/ -std=c99 -g -DCONFIG_ESP_SYSTEM_USE_EH_FRAME -m32
|
||||||
LDFLAGS=-Wl,--eh-frame-hdr -m32 -g -Tlinker.ld -no-pie
|
LDFLAGS=-Wl,--eh-frame-hdr -m32 -g -Tlinker.ld -no-pie
|
||||||
OBJECTS=objs/eh_frame_parser.o objs/main.o
|
OBJECTS=objs/eh_frame_parser.o objs/main.o
|
||||||
HEADERS=eh_frame_parser_impl.h
|
HEADERS=eh_frame_parser_impl.h
|
42
components/esp_system/test_eh_frame_parser/README.md
Normal file
42
components/esp_system/test_eh_frame_parser/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Host test for EH_FRAME_PARSER
|
||||||
|
|
||||||
|
This test is meant to be run on a Linux x86(_64) host. The main purpose is to
|
||||||
|
trigger a SIGSEV (NULL pointer) exception at runtime in order to generate the
|
||||||
|
backtrace. It is then checked that the functions in the call stack are indeed
|
||||||
|
correctly determined in the right order.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
A Linux host, x86 or x86_64. In any case, the example will be compiled with
|
||||||
|
the option `-m32`, it is then required to have gcc multilibs.
|
||||||
|
|
||||||
|
## Compile the example
|
||||||
|
|
||||||
|
To compile the example, simply type:
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run the code
|
||||||
|
|
||||||
|
Execute the binary generated:
|
||||||
|
```
|
||||||
|
./eh_frame_test
|
||||||
|
```
|
||||||
|
|
||||||
|
If everything goes well, the output should be as is:
|
||||||
|
```
|
||||||
|
All tests passed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known issue
|
||||||
|
|
||||||
|
DWARF instructions in `x86` binaries include the instruction `DW_CFA_expression`.
|
||||||
|
However, this instruction is not supported by the parser, this is why the test
|
||||||
|
may print the following message:
|
||||||
|
```
|
||||||
|
Unsupported DWARF opcode 0: 0x10
|
||||||
|
```
|
||||||
|
|
||||||
|
This is not a big problem as the functions implemented for testing the backtrace
|
||||||
|
will not generate unimplemented DWARF instructions.
|
@@ -18,12 +18,12 @@ SECTIONS
|
|||||||
{
|
{
|
||||||
.eh_frame_hdr :
|
.eh_frame_hdr :
|
||||||
{
|
{
|
||||||
_eh_frame_hdr = ABSOLUTE(.);
|
__eh_frame_hdr = ABSOLUTE(.);
|
||||||
*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*)
|
*(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*)
|
||||||
}
|
}
|
||||||
.eh_frame : ONLY_IF_RO
|
.eh_frame : ONLY_IF_RO
|
||||||
{
|
{
|
||||||
_eh_frame = ABSOLUTE(.);
|
__eh_frame = ABSOLUTE(.);
|
||||||
KEEP (*(.eh_frame)) *(.eh_frame.*)
|
KEEP (*(.eh_frame)) *(.eh_frame.*)
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -20,7 +20,8 @@
|
|||||||
* parsing `eh_frame` and `eh_frame_hdr`.
|
* parsing `eh_frame` and `eh_frame_hdr`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 199309L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -29,7 +30,8 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#include "port/eh_frame_parser.h"
|
#include "../include/eh_frame_parser.h"
|
||||||
|
#include "eh_frame_parser_impl.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Index of x86 registers in `greg_t` structure.
|
* @brief Index of x86 registers in `greg_t` structure.
|
||||||
@@ -53,7 +55,12 @@
|
|||||||
* @brief Number which will determine the depth of the call stack.
|
* @brief Number which will determine the depth of the call stack.
|
||||||
* Check `main()` for more information.
|
* Check `main()` for more information.
|
||||||
*/
|
*/
|
||||||
#define NUMBER_TO_TEST (4)
|
#define NUMBER_TO_TEST (4)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of iteration for function `esp_eh_frame_generated_step`.
|
||||||
|
*/
|
||||||
|
#define NUMBER_OF_ITERATION (2 * NUMBER_TO_TEST + 2 + 1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Define a simple linked list type and initialize one.
|
* @brief Define a simple linked list type and initialize one.
|
||||||
@@ -106,7 +113,7 @@ struct functions_info funs[] = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Test whether the address passed as PC is part of the function which
|
* @brief Test whether the address passed as PC is part of the function which
|
||||||
* name is `funciton_name`. The global array `funs` is used.
|
* name is `function_name`. The global array `funs` is used.
|
||||||
*
|
*
|
||||||
* @param pc Program counter to test. (address in the program)
|
* @param pc Program counter to test. (address in the program)
|
||||||
* @param function_name Function name to check the address of.
|
* @param function_name Function name to check the address of.
|
||||||
@@ -126,19 +133,20 @@ bool is_pc_in_function(const uint32_t pc, const char* function_name)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of times `esp_eh_frame_generated_step` is called.
|
||||||
|
*/
|
||||||
|
static uint32_t iteration = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Override the default function called when a backtrace step is
|
* @brief Override the default function called when a backtrace step is
|
||||||
* generated.
|
* generated.
|
||||||
*/
|
*/
|
||||||
void esp_eh_frame_generated_step(uint32_t pc, uint32_t sp) {
|
void esp_eh_frame_generated_step(uint32_t pc, uint32_t sp) {
|
||||||
/* Number of times this function has been entered. */
|
/* The first PCs in the backtrace are calls to `browse_list()` + 2.
|
||||||
static uint32_t iteration = 1;
|
* This is due to the fact that the list contains all the numbers
|
||||||
|
|
||||||
/* The first PC in the backtrace are calls to `browse_list()`.
|
|
||||||
* +2 is due to the fact that the list contains all the numbers
|
|
||||||
* between NUMBER_TO_TEST to 0 included. Moreover, another call
|
* between NUMBER_TO_TEST to 0 included. Moreover, another call
|
||||||
* is made when we meet the NULL pointer.
|
* is made when we meet the NULL pointer. */
|
||||||
*/
|
|
||||||
if (iteration > 0 && iteration <= (NUMBER_TO_TEST + 2)) {
|
if (iteration > 0 && iteration <= (NUMBER_TO_TEST + 2)) {
|
||||||
assert(is_pc_in_function(pc, "browse_list"));
|
assert(is_pc_in_function(pc, "browse_list"));
|
||||||
} else {
|
} else {
|
||||||
@@ -151,13 +159,15 @@ void esp_eh_frame_generated_step(uint32_t pc, uint32_t sp) {
|
|||||||
* browse_list (NUMBER_TO_TEST + 2 iterations), is_even
|
* browse_list (NUMBER_TO_TEST + 2 iterations), is_even
|
||||||
* (NUMBER_TO_TEST/2 calls) and is_odd (NUMBER_TO_TEST/2 calls) calls.
|
* (NUMBER_TO_TEST/2 calls) and is_odd (NUMBER_TO_TEST/2 calls) calls.
|
||||||
*/
|
*/
|
||||||
if (iteration > (2 * NUMBER_TO_TEST + 2))
|
if (iteration >= NUMBER_OF_ITERATION)
|
||||||
return;
|
return;
|
||||||
else if (iteration % 2 == 0)
|
else if (iteration % 2 == 0)
|
||||||
assert(is_pc_in_function(pc, "is_odd"));
|
assert(is_pc_in_function(pc, "is_odd"));
|
||||||
else
|
else
|
||||||
assert(is_pc_in_function(pc, "is_even"));
|
assert(is_pc_in_function(pc, "is_even"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Number of times this function has been entered. */
|
||||||
iteration++;
|
iteration++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +185,7 @@ void signal_handler(int signal, siginfo_t *info, void *ucontext) {
|
|||||||
* Indeed, the registers index defined in ucontext.h are NOT the same
|
* Indeed, the registers index defined in ucontext.h are NOT the same
|
||||||
* the registers index DWARF is expecting. */
|
* the registers index DWARF is expecting. */
|
||||||
ucontext_t* context = (ucontext_t*) ucontext;
|
ucontext_t* context = (ucontext_t*) ucontext;
|
||||||
greg_t *gregset = context->uc_mcontext.__gregs;
|
greg_t *gregset = context->uc_mcontext.gregs;
|
||||||
x86ExcFrame frame = {
|
x86ExcFrame frame = {
|
||||||
.eax = gregset[REG_EAX],
|
.eax = gregset[REG_EAX],
|
||||||
.ecx = gregset[REG_ECX],
|
.ecx = gregset[REG_ECX],
|
||||||
@@ -195,8 +205,15 @@ void signal_handler(int signal, siginfo_t *info, void *ucontext) {
|
|||||||
*/
|
*/
|
||||||
esp_eh_frame_print_backtrace(&frame);
|
esp_eh_frame_print_backtrace(&frame);
|
||||||
|
|
||||||
/* No assert has been triggered, the backtrace succeeded. */
|
/* No assert has been triggered, the backtrace succeeded if the number of
|
||||||
printf("\e[32m\e[1mAll tests passed \e[0m\r\n");
|
* iterations of function `esp_eh_frame_generated_step` is correct. */
|
||||||
|
if (iteration == NUMBER_OF_ITERATION) {
|
||||||
|
printf("\e[32m\e[1mAll tests passed \e[0m\r\n");
|
||||||
|
} else {
|
||||||
|
printf("\e[31m\e[1mWrong length of backtrace (%d iteration, expected %d) \e[0m\r\n",
|
||||||
|
iteration, NUMBER_OF_ITERATION);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Everything went fine, exit normally. */
|
/* Everything went fine, exit normally. */
|
||||||
exit(0);
|
exit(0);
|
||||||
@@ -298,6 +315,7 @@ int main (int argc, char** argv)
|
|||||||
int res = sigaction(SIGSEGV, &sig, NULL);
|
int res = sigaction(SIGSEGV, &sig, NULL);
|
||||||
if (res) {
|
if (res) {
|
||||||
perror("Could not override SIGSEV signal");
|
perror("Could not override SIGSEV signal");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trigger the segmentation fault with a complex backtrace. */
|
/* Trigger the segmentation fault with a complex backtrace. */
|
@@ -1,12 +1,13 @@
|
|||||||
idf_build_get_property(target IDF_TARGET)
|
idf_build_get_property(target IDF_TARGET)
|
||||||
|
|
||||||
set(srcs "wdt_hal_iram.c"
|
set(srcs "wdt_hal_iram.c"
|
||||||
"mpu_hal.c")
|
"mpu_hal.c"
|
||||||
|
"cpu_hal.c")
|
||||||
|
|
||||||
set(includes "${target}/include" "include" "platform_port/include")
|
set(includes "${target}/include" "include" "platform_port/include")
|
||||||
|
|
||||||
if(NOT BOOTLOADER_BUILD)
|
if(NOT BOOTLOADER_BUILD)
|
||||||
list(APPEND srcs
|
list(APPEND srcs
|
||||||
"cpu_hal.c"
|
|
||||||
"rmt_hal.c"
|
"rmt_hal.c"
|
||||||
"rtc_io_hal.c"
|
"rtc_io_hal.c"
|
||||||
"spi_hal.c"
|
"spi_hal.c"
|
||||||
|
@@ -207,6 +207,37 @@ If :doc:`IDF Monitor <tools/idf-monitor>` is used, Program Counter values will b
|
|||||||
MSTATUS : 0x00001881 MTVEC : 0x40380001 MCAUSE : 0x00000007 MTVAL : 0x00000000
|
MSTATUS : 0x00001881 MTVEC : 0x40380001 MCAUSE : 0x00000007 MTVAL : 0x00000000
|
||||||
MHARTID : 0x00000000
|
MHARTID : 0x00000000
|
||||||
|
|
||||||
|
Moreover, it is also capable of generating and printing a backtrace thanks to the stack dump provided by the board in the panic handler.
|
||||||
|
The output looks like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Backtrace:
|
||||||
|
|
||||||
|
0x42006686 in bar (ptr=ptr@entry=0x0) at ../main/hello_world_main.c:18
|
||||||
|
18 *ptr = 0x42424242;
|
||||||
|
#0 0x42006686 in bar (ptr=ptr@entry=0x0) at ../main/hello_world_main.c:18
|
||||||
|
#1 0x42006692 in foo () at ../main/hello_world_main.c:22
|
||||||
|
#2 0x420066ac in app_main () at ../main/hello_world_main.c:28
|
||||||
|
#3 0x42015ece in main_task (args=<optimized out>) at /Users/user/esp/components/freertos/port/port_common.c:142
|
||||||
|
#4 0x403859b8 in vPortEnterCritical () at /Users/user/esp/components/freertos/port/riscv/port.c:130
|
||||||
|
#5 0x00000000 in ?? ()
|
||||||
|
Backtrace stopped: frame did not save the PC
|
||||||
|
|
||||||
|
While this is very handy efficient, it requires the user to use :doc:`IDF Monitor <tools/idf-monitor>`. Thus, in order to generate and print a backtrace while using another monitor program, it is possible to activate :ref:`CONFIG_ESP_SYSTEM_USE_EH_FRAME` option from the menuconfig.
|
||||||
|
|
||||||
|
This option will let the compiler generate DWARF information for each function of the project. Then, when a CPU exception occurs, the panic handler will parse these data and determine the backtrace of the task that failed. The output looks like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Backtrace: 0x42009e9a:0x3fc92120 0x42009ea6:0x3fc92120 0x42009ec2:0x3fc92130 0x42024620:0x3fc92150 0x40387d7c:0x3fc92160 0xfffffffe:0x3fc92170
|
||||||
|
|
||||||
|
These PC:SP pairs represent PC, the Program Counter and SP, the Stack Pointer for each stack frame of the current task.
|
||||||
|
|
||||||
|
|
||||||
|
The main benefit of this option is that this trace is generate by the board itself. Its drawback is that it results in a larger compiled binary, with an increase that can go from 20% to 100%. Finally, it is highly advised to not use this option for production as it results in the presence of debug information within the final binary.
|
||||||
|
|
||||||
|
|
||||||
To find the location where a fatal error has happened, look at the lines which follow the "Backtrace" line. Fatal error location is the top line, and subsequent lines show the call stack.
|
To find the location where a fatal error has happened, look at the lines which follow the "Backtrace" line. Fatal error location is the top line, and subsequent lines show the call stack.
|
||||||
|
|
||||||
.. _GDB-Stub:
|
.. _GDB-Stub:
|
||||||
|
Reference in New Issue
Block a user