Skip to content

Commit

Permalink
espsystem: Rearchitecture and fix eh_frame_parser bugs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
o-marshmallow committed Jul 15, 2021
1 parent b967dc0 commit 0771bd1
Show file tree
Hide file tree
Showing 24 changed files with 293 additions and 111 deletions.
7 changes: 7 additions & 0 deletions .gitlab/ci/host-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,10 @@ test_log:
- cd ${IDF_PATH}/components/log/host_test/log_test
- idf.py build
- 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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ unset(link_options)

# Add the following build specifications here, since these seem to be dependent
# on config values on the root Kconfig.

if(NOT BOOTLOADER_BUILD)

if(CONFIG_COMPILER_OPTIMIZATION_SIZE)
Expand Down
4 changes: 2 additions & 2 deletions components/esp32c3/ld/esp32c3.ld
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,6 @@ REGION_ALIAS("rtc_data_location", rtc_iram_seg );
#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!");
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
24 changes: 15 additions & 9 deletions components/esp32c3/ld/esp32c3.project.ld.in
Original file line number Diff line number Diff line change
Expand Up @@ -339,22 +339,28 @@ SECTIONS
*(.srodata.*)
_thread_local_end = ABSOLUTE(.);
_rodata_reserved_end = ABSOLUTE(.);
. = ALIGN(4);
. = ALIGN(ALIGNOF(.eh_frame));
} > 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))
_eh_frame_end = ABSOLUTE(.);
} > drom0_0_seg
__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

.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))
_eh_frame_hdr_end = ABSOLUTE(.);
} > drom0_0_seg
__eh_frame_hdr_end = ABSOLUTE(.);
} > default_rodata_seg

.flash.rodata_noload (NOLOAD) :
{
Expand Down
5 changes: 5 additions & 0 deletions components/esp32h2/ld/esp32h2.ld
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,8 @@ REGION_ALIAS("rtc_data_location", rtc_iram_seg );
ASSERT(_flash_rodata_dummy_start == ORIGIN(default_rodata_seg),
".flash_rodata_dummy section must be placed at the beginning of the rodata segment.")
#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
25 changes: 21 additions & 4 deletions components/esp32h2/ld/esp32h2.project.ld.in
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,6 @@ SECTIONS
*(.gcc_except_table .gcc_except_table.*)
*(.gnu.linkonce.e.*)
*(.gnu.version_r)
. = (. + 3) & ~ 3;
__eh_frame = ABSOLUTE(.);
KEEP(*(.eh_frame))
. = (. + 7) & ~ 3;
/*
* C++ constructor and destructor tables
Expand Down Expand Up @@ -342,7 +339,27 @@ SECTIONS
*(.srodata.*)
_thread_local_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

/* Marks the end of IRAM code segment */
Expand Down
6 changes: 5 additions & 1 deletion components/esp_hw_support/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
idf_build_get_property(target IDF_TARGET)

set(priv_requires efuse)
set(requires soc)
if(${target} STREQUAL "esp32")
list(APPEND requires efuse)
Expand All @@ -17,7 +18,10 @@ if(NOT BOOTLOADER_BUILD)
"mac_addr.c"
"sleep_modes.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()

idf_component_register(SRCS ${srcs}
Expand Down
11 changes: 8 additions & 3 deletions components/esp_system/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
idf_build_get_property(target IDF_TARGET)

set(srcs)
set(srcs "esp_err.c")

if(CONFIG_IDF_ENV_FPGA)
list(APPEND srcs "fpga_overrides.c")
endif()

if(BOOTLOADER_BUILD)
# Bootloader relies on some Kconfig options defined in esp_system.
idf_component_register(SRCS "${srcs}")
# "_esp_error_check_failed()" requires spi_flash module
# Bootloader relies on some Kconfig options defined in esp_system.
idf_component_register(SRCS "${srcs}" REQUIRES spi_flash)
else()
list(APPEND srcs "crosscore_int.c"
"esp_err.c"
Expand All @@ -26,6 +27,10 @@ else()
list(APPEND srcs "dbg_stubs.c")
endif()

if(CONFIG_ESP_SYSTEM_USE_EH_FRAME)
list(APPEND srcs "eh_frame_parser.c")
endif()

idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS include
PRIV_REQUIRES spi_flash
Expand Down
10 changes: 6 additions & 4 deletions components/esp_system/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,12 @@ menu "ESP System Settings"
default n
depends on IDF_TARGET_ARCH_RISCV
help
Generate DWARF information in the resulting binary to perform backtracing when panics occur. Activating
this option will activate asynchronous frame unwinding and generation of both .eh_frame and .eh_frame_hdr
sections, resulting in a bigger binary size (20% to 100% larger). This option shall be not be used for
production.
Generate DWARF information for each function of the project. These information will parsed and used to
perform backtracing when panics occur. Activating this option will activate asynchronous frame unwinding
and generation of both .eh_frame and .eh_frame_hdr sections, resulting in a bigger binary size (20% to
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"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@
* http://dwarfstd.org/Download.php
*/

#include "port/eh_frame_parser.h"
#include "port/eh_frame_parser_impl.h"
#include "eh_frame_parser.h"
#include "esp_private/panic_internal.h"
#include <string.h>

#if CONFIG_ESP_SYSTEM_USE_EH_FRAME

#include "eh_frame_parser_impl.h"

/**
* @brief Dimension of an array (number of elements)
*/
Expand Down Expand Up @@ -90,8 +91,8 @@
/**
* @brief Pointers to both .eh_frame_hdr and .eh_frame sections.
*/
#define EH_FRAME_HDR_ADDR (&_eh_frame_hdr)
#define EH_FRAME_ADDR (&_eh_frame)
#define EH_FRAME_HDR_ADDR (&__eh_frame_hdr)
#define EH_FRAME_ADDR (&__eh_frame)

/**
* @brief Structure of .eh_frame_hdr section header.
Expand Down Expand Up @@ -259,8 +260,8 @@ typedef struct {
* @brief Symbols defined by the linker.
* Retrieve the addresses of both .eh_frame_hdr and .eh_frame sections.
*/
extern char _eh_frame_hdr;
extern char _eh_frame;
extern char __eh_frame_hdr;
extern char __eh_frame;

/**
* @brief Decode multiple bytes encoded in LEB128.
Expand Down Expand Up @@ -439,22 +440,22 @@ static const table_entry* esp_eh_frame_find_entry(const table_entry* sorted_tabl
/* Signed comparisons. */
const int32_t sfun_addr = (int32_t) fun_addr;
const int32_t snxt_addr = (int32_t) nxt_addr;
if (sfun_addr <= ra && snxt_addr > ra)
found = true;
else if (snxt_addr <= ra)
begin = middle + 1;
else
end = middle;
if (sfun_addr <= ra && snxt_addr > ra)
found = true;
else if (snxt_addr <= ra)
begin = middle + 1;
else
end = middle;

} else {
/* Unsigned comparisons. */
const uint32_t ura = (uint32_t) ra;
if (fun_addr <= ura && nxt_addr > ura)
found = true;
else if (nxt_addr <= ura)
begin = middle + 1;
else
end = middle;
if (fun_addr <= ura && nxt_addr > ura)
found = true;
else if (nxt_addr <= ura)
begin = middle + 1;
else
end = middle;
}

middle = (end + begin) / 2;
Expand Down Expand Up @@ -799,10 +800,45 @@ static uint32_t esp_eh_frame_restore_caller_state(const uint32_t* fde,
EXECUTION_FRAME_SP(*frame) = cfa_addr;

/* If the frame was not available, it would be possible to retrieve the return address
* register thanks to CIE structure. */
return EXECUTION_FRAME_REG(frame, ra_reg);
* register thanks to CIE structure.
* 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.
Expand All @@ -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.
*/
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 };
ExecutionFrame frame = *frame_or;
ExecutionFrame frame = *((ExecutionFrame*) frame_or);
uint32_t size = 0;
uint8_t* enc_values = NULL;
bool end_of_backtrace = false;
Expand Down Expand Up @@ -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,
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. */
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;
}

/* Get absolute address of FDE entry describing the function where PC left of. */
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. */
/* Clean and set the DWARF register structure. */
memset(&state, 0, sizeof(dwarf_regs));

const uint32_t prev_sp = EXECUTION_FRAME_SP(frame);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,21 @@
#ifndef 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.
*
* @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
8 changes: 7 additions & 1 deletion components/esp_system/port/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)

set(srcs "cpu_start.c" "panic_handler.c" "brownout.c")
Expand Down
Loading

0 comments on commit 0771bd1

Please sign in to comment.