From eeaa40f71d1acd257e92fa7e098bb9465c0e8bbd Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Mon, 16 May 2022 18:27:45 +0800 Subject: [PATCH] System: implement libunwind library for RISC-V backtracing Closes https://github.com/espressif/esp-idf/issues/7866 A minimal x86 implementation has also been added, it is used to perform a host test. --- components/esp_system/eh_frame_parser.c | 132 ++++++++++- .../include/esp_private/eh_frame_parser.h | 3 +- components/esp_system/include/libunwind.h | 135 +++++++++++ .../port/include/riscv/eh_frame_parser_impl.h | 64 ------ .../port/include/riscv/libunwind-riscv.h | 213 ++++++++++++++++++ .../port/include/x86/libunwind-x86.h | 146 ++++++++++++ .../esp_system/test_eh_frame_parser/Makefile | 19 +- .../eh_frame_parser_impl.h | 83 ------- .../esp_system/test_eh_frame_parser/main.c | 140 ++++++++++-- .../test_eh_frame_parser/sdkconfig.h | 12 + 10 files changed, 760 insertions(+), 187 deletions(-) create mode 100644 components/esp_system/include/libunwind.h delete mode 100644 components/esp_system/port/include/riscv/eh_frame_parser_impl.h create mode 100644 components/esp_system/port/include/riscv/libunwind-riscv.h create mode 100644 components/esp_system/port/include/x86/libunwind-x86.h delete mode 100644 components/esp_system/test_eh_frame_parser/eh_frame_parser_impl.h create mode 100644 components/esp_system/test_eh_frame_parser/sdkconfig.h diff --git a/components/esp_system/eh_frame_parser.c b/components/esp_system/eh_frame_parser.c index 5e8fefffa7..363c9f1142 100644 --- a/components/esp_system/eh_frame_parser.c +++ b/components/esp_system/eh_frame_parser.c @@ -15,14 +15,18 @@ * found in the official documentation: * http://dwarfstd.org/Download.php */ - -#include "esp_private/eh_frame_parser.h" -#include "esp_private/panic_internal.h" +#include "sdkconfig.h" #include #if CONFIG_ESP_SYSTEM_USE_EH_FRAME -#include "eh_frame_parser_impl.h" +#include "libunwind.h" +#include "esp_private/panic_internal.h" +#include "esp_private/eh_frame_parser.h" + +#if UNW_UNKNOWN_TARGET + #error "Unsupported architecture for unwinding" +#endif /** * @brief Dimension of an array (number of elements) @@ -928,4 +932,124 @@ void esp_eh_frame_print_backtrace(const void *frame_or) panic_print_str("\r\n"); } + +/** + * The following functions are the implementation of libunwind API + * Check the header libunwind.h for more information + */ + +int unw_init_local(unw_cursor_t* c, unw_context_t* ctxt) { + /* In our implementation, a context and a cursor is the same, so we simply need + * to copy a structure inside another one */ + _Static_assert(sizeof(unw_cursor_t) >= sizeof(unw_context_t), "unw_cursor_t size must be greater or equal to unw_context_t's"); + int ret = -UNW_EUNSPEC; + if (c != NULL && ctxt != NULL) { + memcpy(c, ctxt, sizeof(unw_context_t)); + ret = UNW_ESUCCESS; + } + return ret; +} + +int unw_step(unw_cursor_t* cp) { + static dwarf_regs state = { 0 }; + ExecutionFrame* frame = (ExecutionFrame*) cp; + uint32_t size = 0; + uint8_t* enc_values = NULL; + + /* Start parsing the .eh_frame_hdr section. */ + fde_header* header = (fde_header*) EH_FRAME_HDR_ADDR; + if (header->version != 1) { + goto badversion; + } + + /* Make enc_values point to the end of the structure, where the encoded + * values start. */ + enc_values = (uint8_t*) (header + 1); + + /* Retrieve the encoded value eh_frame_ptr. Get the size of the data also. */ + const uint32_t eh_frame_ptr = esp_eh_frame_get_encoded(enc_values, header->eh_frame_ptr_enc, &size); + assert(eh_frame_ptr == (uint32_t) EH_FRAME_ADDR); + enc_values += size; + + /* Same for the number of entries in the sorted table. */ + const uint32_t fde_count = esp_eh_frame_get_encoded(enc_values, header->fde_count_enc, &size); + enc_values += size; + + /* enc_values points now at the beginning of the sorted table. */ + /* Only support 4-byte entries. */ + const uint32_t table_enc = header->table_enc; + if ( ((table_enc >> 4) != 0x3) && ((table_enc >> 4) != 0xB) ) { + goto badversion; + } + + const table_entry* sorted_table = (const table_entry*) enc_values; + + const table_entry* from_fun = esp_eh_frame_find_entry(sorted_table, fde_count, + table_enc, EXECUTION_FRAME_PC(*frame)); + + /* 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))) { + goto missinginfo; + } + + const uint32_t prev_sp = EXECUTION_FRAME_SP(*frame); + + /* Retrieve the return address of the frame. The frame's registers will be modified. + * The frame we get then is the caller's one. */ + uint32_t ra = esp_eh_frame_restore_caller_state(fde, frame, &state); + + /* End of backtrace is reached if the stack and the PC don't change anymore. */ + if ((EXECUTION_FRAME_SP(*frame) == prev_sp) && (EXECUTION_FRAME_PC(*frame) == ra)) { + goto stopunwind; + } + + /* Go back to the caller: update stack pointer and program counter. */ + EXECUTION_FRAME_PC(*frame) = ra; + + return 1; +badversion: + return -UNW_EBADVERSION; +missinginfo: + return -UNW_ENOINFO; +stopunwind: + return 0; +} + +int unw_get_reg(unw_cursor_t* cp, unw_regnum_t reg, unw_word_t* valp) { + if (cp == NULL || valp == NULL) { + goto invalid; + } + if (reg >= EXECUTION_FRAME_MAX_REGS) { + goto badreg; + } + + *valp = EXECUTION_FRAME_REG(cp, reg); + return UNW_ESUCCESS; +invalid: + return -UNW_EUNSPEC; +badreg: + return -UNW_EBADREG; +} + +int unw_set_reg(unw_cursor_t* cp, unw_regnum_t reg, unw_word_t val) { + if (cp == NULL) { + goto invalid; + } + if (reg >= EXECUTION_FRAME_MAX_REGS) { + goto badreg; + } + + EXECUTION_FRAME_REG(cp, reg) = val; + return UNW_ESUCCESS; +invalid: + return -UNW_EUNSPEC; +badreg: + return -UNW_EBADREG; +} + #endif //ESP_SYSTEM_USE_EH_FRAME diff --git a/components/esp_system/include/esp_private/eh_frame_parser.h b/components/esp_system/include/esp_private/eh_frame_parser.h index a9b70aab1c..bd77963b4e 100644 --- a/components/esp_system/include/esp_private/eh_frame_parser.h +++ b/components/esp_system/include/esp_private/eh_frame_parser.h @@ -24,4 +24,5 @@ void esp_eh_frame_print_backtrace(const void *frame_or); } #endif -#endif + +#endif // EH_FRAME_PARSER_H diff --git a/components/esp_system/include/libunwind.h b/components/esp_system/include/libunwind.h new file mode 100644 index 0000000000..a91de95965 --- /dev/null +++ b/components/esp_system/include/libunwind.h @@ -0,0 +1,135 @@ +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef LIBUNWIND_H +#define LIBUNWIND_H + +#include "sdkconfig.h" +#include +#include + +#if CONFIG_IDF_TARGET_ARCH_RISCV + #include "libunwind-riscv.h" +#elif CONFIG_IDF_TARGET_X86 + #include "libunwind-x86.h" +#else + /* This header must be a standalone one, so, it shall not trigger an error when + * pre-processed without including any of the architecture header above. + * The implementation can trigger a compile error if UNW_UNKNOWN_TARGET + * macro is defined. */ + #define UNW_UNKNOWN_TARGET 1 + typedef void* ExecutionFrame; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Error codes returned by the functions defined below */ +#define UNW_ESUCCESS 0 +#define UNW_EUNSPEC 1 /* General failure */ +#define UNW_EBADREG 3 /* Register given is wrong */ +#define UNW_ESTOPUNWIND 5 +#define UNW_EINVAL 8 /* Bad parameter or unimplemented operation */ +#define UNW_EBADVERSION 9 +#define UNW_ENOINFO 10 + +/* A libunwind context is the equivalent of an ESP-IDF ExecutionFrame */ +typedef ExecutionFrame unw_context_t; + +/* A register number is an unsigned word in our case */ +typedef uint32_t unw_regnum_t; + +/* In our current implementation, a cursor is the same as a context */ +typedef unw_context_t unw_cursor_t; + +/* long should represent the size of a CPU register */ +typedef unsigned long unw_word_t; + +/* At the moment, we don't support the operations using the following types, + * so just set them to void* */ +typedef void* unw_addr_space_t; +typedef void* unw_fpreg_t; + +/** + * @brief Get the current CPU context. + * + * @param[out] ctx Pointer to `unw_context_t` structure. It must not be NULL + * as it will be filled with the CPU registers value + * + * @return UNW_ESUCCESS on success, -UNW_EUNSPEC if ctx is NULL + * + * @note This function MUST be inlined. Marking it as "static inline" or + * __attribute__((always_inline)) does not guarantee that it will inlined by + * the compiler for all the architectures. Thus, define this function as a macro. + * @note If the caller of this function returns, all the pointers, contexts, cursors + * generated out of the initial returned context shall be considered invalid and + * thus, must **not** be used. + */ +#define unw_getcontext(ctx) ({ int retval; \ + if (ctx == NULL) { \ + retval = -UNW_EUNSPEC; \ + } else { \ + UNW_GET_CONTEXT(ctx); \ + retval = UNW_ESUCCESS; \ + } \ + retval; \ + }) + +/** + * @brief Initialize a cursor on a local context. Multiple cursor can be initialized on + * a given CPU context, they can then be manipulated independently. + * + * @param[out] c Pointer on cursor to be returned. Must not be NULL + * @param[in] ctx Pointer on the context returned by the function `unw_getcontext` + * + * @return UNW_ESUCCESS on success, -UNW_EUNSPEC if one of the parameter is NULL. + */ +int unw_init_local(unw_cursor_t* c, unw_context_t* ctx); + +/** + * @brief Perform a step "up" on the given cursor. After calling this function, the + * cursor will point to the caller's CPU context. Thus, it is then possible + * to retrieve the caller's address by getting the PC register out of the cursor. + * Check `unw_get_reg` function for this. + * + * @param[in] cp Current cursor + * + * @returns 0 if the previous frame was the last one + * @returns Positive value on success + * @returns -UNW_EBADVERSION if the DWARF information's version is not compatible with the eh_frame_parser implementation + * @returns -UNW_ENOINFO if the caller information are not present in the binary. (if the caller is in ROM for example) + * @returns -UNW_ESTOPUNWIND if unwinding is terminated + */ +int unw_step(unw_cursor_t* cp); + +/** + * @brief Get the value of a CPU register from a given cursor. + * + * @param[in] cp Pointer to the cursor + * @param reg Register number to retrieve the value of + * @param[out] valp Pointer that will be filled with the register value + * + * @returns UNW_ESUCCESS on success + * @returns -UNW_EUNSPEC if any pointer passed is NULL + * @returns -UNW_EBADREG if the register number is invalid + */ +int unw_get_reg(unw_cursor_t* cp, unw_regnum_t reg, unw_word_t* valp); + +/** + * @brief Set the value of a CPU register in a given cursor. + * + * @param[in]cp Pointer to the cursor + * @param reg Register number to set the value of + * @param val New register value + * + * @returns UNW_ESUCCESS on success + * @returns -UNW_EUNSPEC if the pointer passed is NULL + * @returns -UNW_EBADREG if the register number is invalid + */ +int unw_set_reg(unw_cursor_t* cp, unw_regnum_t reg, unw_word_t val); + +#endif // LIBUNWIND_H diff --git a/components/esp_system/port/include/riscv/eh_frame_parser_impl.h b/components/esp_system/port/include/riscv/eh_frame_parser_impl.h deleted file mode 100644 index d2a4784627..0000000000 --- a/components/esp_system/port/include/riscv/eh_frame_parser_impl.h +++ /dev/null @@ -1,64 +0,0 @@ - -/* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * @file DWARF Exception Frames parser header - * - * This file describes the frame types for RISC-V, required for - * parsing `eh_frame` and `eh_frame_hdr`. - * - */ - -#pragma once - -#include "riscv/rvruntime-frames.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Define the Executionframe as RvExcFrame for this implementation. - */ -typedef RvExcFrame ExecutionFrame; - -/** - * @brief Number of registers in the ExecutionFrame structure. - * - * This will be used to define and initialize the DWARF machine state. - * In practice, we only have 16 registers that are callee saved, thus, we could - * only save them and ignore the rest. However, code to calculate mapping of - * CPU registers to DWARF registers would take more than the 16 registers we - * would save... so save all registers. - */ -#define EXECUTION_FRAME_MAX_REGS (32) - -/** - * @brief Reference the PC register of the execution frame. - */ -#define EXECUTION_FRAME_PC(frame) ((frame).mepc) - -/** - * @brief Reference the SP register of the execution frame. - */ -#define EXECUTION_FRAME_SP(frame) ((frame).sp) - -/** - * @brief Index of SP register in the execution frame. - */ -#define EXECUTION_FRAME_SP_REG (offsetof(RvExcFrame, sp)/sizeof(uint32_t)) - -/** - * @brief Get register i of the execution frame. - */ -#define EXECUTION_FRAME_REG(frame, i) (((uint32_t*) (frame))[(i)]) - -#ifdef __cplusplus -} -#endif - -// #endif // _EH_FRAME_PARSER_IMPL_H diff --git a/components/esp_system/port/include/riscv/libunwind-riscv.h b/components/esp_system/port/include/riscv/libunwind-riscv.h new file mode 100644 index 0000000000..5a02accb29 --- /dev/null +++ b/components/esp_system/port/include/riscv/libunwind-riscv.h @@ -0,0 +1,213 @@ + +/* + * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * This file describes the frame types for RISC-V, required for + * parsing `eh_frame` and `eh_frame_hdr`, and more generally libunwind. + */ + +#pragma once + +#include +#include "esp_attr.h" +#include "riscv/rvruntime-frames.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Define the size of a CPU register. + */ +#define ARCH_WORD_SIZE (sizeof(long)) + +/** + * @brief Retrive the index of a field inside a structure. All the fields + * must have a word size. + */ +#define indexof(structure,field) (offsetof(structure, field) / ARCH_WORD_SIZE) + +/** + * @brief Define the Executionframe as RvExcFrame for this implementation. + */ +typedef RvExcFrame ExecutionFrame; + +/** + * @brief Enumeration of all the registers for RISC-V architecture + */ +typedef enum { + UNW_RISCV_PC = indexof(ExecutionFrame, mepc), + UNW_RISCV_RA = indexof(ExecutionFrame, ra), + UNW_RISCV_SP = indexof(ExecutionFrame, sp), + UNW_RISCV_GP = indexof(ExecutionFrame, gp), + UNW_RISCV_TP = indexof(ExecutionFrame, tp), + UNW_RISCV_T0 = indexof(ExecutionFrame, t0), + UNW_RISCV_T1 = indexof(ExecutionFrame, t1), + UNW_RISCV_T2 = indexof(ExecutionFrame, t2), + UNW_RISCV_S0 = indexof(ExecutionFrame, s0), + UNW_RISCV_S1 = indexof(ExecutionFrame, s1), + UNW_RISCV_A0 = indexof(ExecutionFrame, a0), + UNW_RISCV_A1 = indexof(ExecutionFrame, a1), + UNW_RISCV_A2 = indexof(ExecutionFrame, a2), + UNW_RISCV_A3 = indexof(ExecutionFrame, a3), + UNW_RISCV_A4 = indexof(ExecutionFrame, a4), + UNW_RISCV_A5 = indexof(ExecutionFrame, a5), + UNW_RISCV_A6 = indexof(ExecutionFrame, a6), + UNW_RISCV_A7 = indexof(ExecutionFrame, a7), + UNW_RISCV_S2 = indexof(ExecutionFrame, s2), + UNW_RISCV_S3 = indexof(ExecutionFrame, s3), + UNW_RISCV_S4 = indexof(ExecutionFrame, s4), + UNW_RISCV_S5 = indexof(ExecutionFrame, s5), + UNW_RISCV_S6 = indexof(ExecutionFrame, s6), + UNW_RISCV_S7 = indexof(ExecutionFrame, s7), + UNW_RISCV_S8 = indexof(ExecutionFrame, s8), + UNW_RISCV_S9 = indexof(ExecutionFrame, s9), + UNW_RISCV_S10 = indexof(ExecutionFrame, s10), + UNW_RISCV_S11 = indexof(ExecutionFrame, s11), + UNW_RISCV_T3 = indexof(ExecutionFrame, t3), + UNW_RISCV_T4 = indexof(ExecutionFrame, t4), + UNW_RISCV_T5 = indexof(ExecutionFrame, t5), + UNW_RISCV_T6 = indexof(ExecutionFrame, t6), + UNW_RISCV_MSTATUS = indexof(ExecutionFrame, mstatus), + UNW_RISCV_MTVEC = indexof(ExecutionFrame, mtvec), + UNW_RISCV_MCAUSE = indexof(ExecutionFrame, mcause), + UNW_RISCV_MTVAL = indexof(ExecutionFrame, mtval), + UNW_RISCV_MHARTID = indexof(ExecutionFrame, mhartid), +} riscv_regnum_t; + +/** + * @brief Number of registers in the ExecutionFrame structure. + * + * This will be used to define and initialize the DWARF machine state. + * In practice, we only have 16 registers that are callee saved, thus, we could + * only save them and ignore the rest. However, code to calculate mapping of + * CPU registers to DWARF registers would take more than the 16 registers we + * would save... so save all registers. + */ +#define EXECUTION_FRAME_MAX_REGS (32) + +/** + * @brief Reference the PC register of the execution frame. + */ +#define EXECUTION_FRAME_PC(frame) ((frame).mepc) + +/** + * @brief Reference the SP register of the execution frame. + */ +#define EXECUTION_FRAME_SP(frame) ((frame).sp) + +/** + * @brief Index of SP register in the execution frame. + */ +#define EXECUTION_FRAME_SP_REG (indexof(RvExcFrame, sp)) + +/** + * @brief Get register i of the execution frame. + */ +#define EXECUTION_FRAME_REG(frame, i) (((uint32_t*) (frame))[(i)]) + +/** + * @brief Get the current context + */ +FORCE_INLINE_ATTR void UNW_GET_CONTEXT(ExecutionFrame* frame) { + __asm__ __volatile__("sw t0, %1(%0)\n" + "auipc t0, 0\n" + "sw t0, %2(%0)\n" + "sw ra, %3(%0)\n" + "sw sp, %4(%0)\n" + "sw gp, %5(%0)\n" + "sw tp, %6(%0)\n" + "sw t1, %7(%0)\n" + "sw t2, %8(%0)\n" + "sw s0, %9(%0)\n" + "sw s1, %10(%0)\n" + "sw a0, %11(%0)\n" + "sw a1, %12(%0)\n" + "sw a2, %13(%0)\n" + "sw a3, %14(%0)\n" + "sw a4, %15(%0)\n" + "sw a5, %16(%0)\n" + "sw a6, %17(%0)\n" + "sw a7, %18(%0)\n" + "sw s2, %19(%0)\n" + "sw s3, %20(%0)\n" + "sw s4, %21(%0)\n" + "sw s5, %22(%0)\n" + "sw s6, %23(%0)\n" + "sw s7, %24(%0)\n" + "sw s8, %25(%0)\n" + "sw s9, %26(%0)\n" + "sw s10, %27(%0)\n" + "sw s11, %28(%0)\n" + "sw t3, %29(%0)\n" + : + : "r" (frame), + "i" (UNW_RISCV_T0 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_PC * ARCH_WORD_SIZE), + "i" (UNW_RISCV_RA * ARCH_WORD_SIZE), + "i" (UNW_RISCV_SP * ARCH_WORD_SIZE), + "i" (UNW_RISCV_GP * ARCH_WORD_SIZE), + "i" (UNW_RISCV_TP * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T1 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T2 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S0 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S1 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A0 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A1 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A2 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A3 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A4 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A5 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A6 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_A7 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S2 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S3 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S4 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S5 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S6 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S7 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S8 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S9 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S10 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_S11 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T3 * ARCH_WORD_SIZE) + ); + /* GCC doesn't allow us to have more than 30 operands in a single + * __asm__ __volatile__ definition, so we have to split it into 2 */ + __asm__ __volatile__("sw t4, %1(%0)\n" + "sw t5, %2(%0)\n" + "sw t6, %3(%0)\n" + "csrr t0, mstatus\n" + "sw t0, %4(%0)\n" + "csrr t0, mtvec\n" + "sw t0, %5(%0)\n" + "csrr t0, mcause\n" + "sw t0, %6(%0)\n" + "csrr t0, mtval\n" + "sw t0, %7(%0)\n" + "csrr t0, mhartid\n" + "sw t0, %8(%0)\n" + /* We have to restore t0 as it may be in use by the function that makes the use of this assembly snippet */ + "lw t0, %9(%0)\n" + : + : "r" (frame), + "i" (UNW_RISCV_T4 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T5 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T6 * ARCH_WORD_SIZE), + "i" (UNW_RISCV_MSTATUS * ARCH_WORD_SIZE), + "i" (UNW_RISCV_MTVEC * ARCH_WORD_SIZE), + "i" (UNW_RISCV_MCAUSE * ARCH_WORD_SIZE), + "i" (UNW_RISCV_MTVAL * ARCH_WORD_SIZE), + "i" (UNW_RISCV_MHARTID * ARCH_WORD_SIZE), + "i" (UNW_RISCV_T0 * ARCH_WORD_SIZE) + ); +} + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/port/include/x86/libunwind-x86.h b/components/esp_system/port/include/x86/libunwind-x86.h new file mode 100644 index 0000000000..9712e39571 --- /dev/null +++ b/components/esp_system/port/include/x86/libunwind-x86.h @@ -0,0 +1,146 @@ + +/* + * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Define the size of a CPU register. + */ +#define ARCH_WORD_SIZE (sizeof(long)) + +/** + * @brief Retrieve the index of a field inside a structure. All the fields + * must have a word size. + */ +#define indexof(structure,field) (offsetof(structure, field) / ARCH_WORD_SIZE) + +/** + * @brief Number of registers in the ExecutionFrame structure. + */ +#define EXECUTION_FRAME_MAX_REGS (11) + +/** + * @brief Definition of the x86 DWARF registers set. + * The following registers order has been taken from GCC's `i386.c` file: + */ +typedef struct x86ExcFrame +{ + union { + struct { + uint32_t eax; + uint32_t ecx; + uint32_t edx; + uint32_t ebx; + uint32_t esp; + uint32_t ebp; + uint32_t esi; + uint32_t edi; + uint32_t eip; + uint32_t eflags; + uint32_t trapno; + }; + uint32_t registers[EXECUTION_FRAME_MAX_REGS]; + }; +} x86ExcFrame; + +/** + * @brief Define the Executionframe as RvExcFrame for this implementation. + */ +typedef x86ExcFrame ExecutionFrame; + +/** + * @brief Enumeration of the registers for x86 (32-bit) architecture + */ +typedef enum { + UNW_X86_EAX = indexof(ExecutionFrame, eax), + UNW_X86_ECX = indexof(ExecutionFrame, ecx), + UNW_X86_EDX = indexof(ExecutionFrame, edx), + UNW_X86_EBX = indexof(ExecutionFrame, ebx), + UNW_X86_ESP = indexof(ExecutionFrame, esp), + UNW_X86_EBP = indexof(ExecutionFrame, ebp), + UNW_X86_ESI = indexof(ExecutionFrame, esi), + UNW_X86_EDI = indexof(ExecutionFrame, edi), + UNW_X86_EIP = indexof(ExecutionFrame, eip), + UNW_X86_EFLAGS = indexof(ExecutionFrame, eflags), + UNW_X86_TRAPNO = indexof(ExecutionFrame, trapno), +} x86_regnum_t; + +/** + * @brief Reference the PC register of the execution frame + */ +#define EXECUTION_FRAME_PC(struct) ((struct).eip) + +/** + * @brief Reference the SP register of the execution frame + */ +#define EXECUTION_FRAME_SP(struct) ((struct).esp) + +/** + * @brief Index of SP register in the execution frame. + */ +#define EXECUTION_FRAME_SP_REG (indexof(x86ExcFrame, esp)) + +/** + * @brief Get register i of the execution frame + */ +#define EXECUTION_FRAME_REG(frame, i) ((frame)->registers[(i)]) + +/** + * @brief Get the current context + * + * @note For x86, this needs to be a macro, else, the compiler will not inline it, + * even if we specify __attribute__((always_inline)) + * + * @param ExecutionFrame* Pointer to the frame/context to fill + */ +#define UNW_GET_CONTEXT(frame) { \ + __asm__ __volatile__(".intel_syntax noprefix\n\t" \ + "mov DWORD PTR [%0 + %c1], eax\n\t" \ + "mov DWORD PTR [%0 + %c2], ecx\n\t" \ + "mov DWORD PTR [%0 + %c3], edx\n\t" \ + "mov DWORD PTR [%0 + %c4], ebx\n\t" \ + "mov DWORD PTR [%0 + %c5], esp\n\t" \ + "mov DWORD PTR [%0 + %c6], ebp\n\t" \ + "mov DWORD PTR [%0 + %c7], esi\n\t" \ + "mov DWORD PTR [%0 + %c8], edi\n\t" \ + "mov DWORD PTR [%0 + %c11], 0\n\t" \ + /* Special part for retrieving PC */ \ + "call __get_pc\n" \ + "__get_pc: pop ebx\n\t" \ + "mov DWORD PTR [%0 + %c9], ebx\n\t" \ + /* Same for the flags */ \ + "pushfd\n\t" \ + "pop ebx\n\t" \ + "mov DWORD PTR [%0 + %c10], ebx\n\t" \ + /* Restore EBX */ \ + "mov ebx, [%0 + %c4]\n\t" \ + ".att_syntax prefix" \ + : \ + : "r" (frame), \ + "i" (UNW_X86_EAX * ARCH_WORD_SIZE), \ + "i" (UNW_X86_ECX * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EDX * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EBX * ARCH_WORD_SIZE), \ + "i" (UNW_X86_ESP * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EBP * ARCH_WORD_SIZE), \ + "i" (UNW_X86_ESI * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EDI * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EIP * ARCH_WORD_SIZE), \ + "i" (UNW_X86_EFLAGS * ARCH_WORD_SIZE), \ + "i" (UNW_X86_TRAPNO * ARCH_WORD_SIZE) \ + ); \ +} + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_system/test_eh_frame_parser/Makefile b/components/esp_system/test_eh_frame_parser/Makefile index 6a51f89fd9..1314e0dcb2 100644 --- a/components/esp_system/test_eh_frame_parser/Makefile +++ b/components/esp_system/test_eh_frame_parser/Makefile @@ -1,20 +1,9 @@ -# -# 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. + # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + # + # SPDX-License-Identifier: Apache-2.0 CC=gcc -CFLAGS=-W -fasynchronous-unwind-tables -I. -I../include/ -std=c99 -g -DCONFIG_ESP_SYSTEM_USE_EH_FRAME -m32 +CFLAGS=-W -fasynchronous-unwind-tables -I. -I../port/include/x86/ -I../include/ -I../include/esp_private -std=c99 -g -m32 LDFLAGS=-Wl,--eh-frame-hdr -m32 -g -Tlinker.ld -no-pie OBJECTS=objs/eh_frame_parser.o objs/main.o HEADERS=eh_frame_parser_impl.h diff --git a/components/esp_system/test_eh_frame_parser/eh_frame_parser_impl.h b/components/esp_system/test_eh_frame_parser/eh_frame_parser_impl.h deleted file mode 100644 index 68b211b657..0000000000 --- a/components/esp_system/test_eh_frame_parser/eh_frame_parser_impl.h +++ /dev/null @@ -1,83 +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. - -/** - * @file DWARF Exception Frames parser header - * - * This file describes the frame types for x86, required for - * parsing `eh_frame` and `eh_frame_hdr`. - */ - -#ifndef EH_FRAME_PARSER_IMPL_H -#define EH_FRAME_PARSER_IMPL_H - -#include -#include - -/** - * @brief Number of registers in the ExecutionFrame structure. - */ -#define EXECUTION_FRAME_MAX_REGS (11) - -/** - * @brief Definition of the x86 DWARF tegisters set. - * The following registers order has been taken from GCC's `i386.c` file: - */ -typedef struct x86ExcFrame -{ - union { - struct { - uint32_t eax; - uint32_t ecx; - uint32_t edx; - uint32_t ebx; - uint32_t esp; - uint32_t ebp; - uint32_t esi; - uint32_t edi; - uint32_t eip; - uint32_t eflags; - uint32_t trapno; - }; - uint32_t registers[EXECUTION_FRAME_MAX_REGS]; - }; -} x86ExcFrame; - -/** - * @brief Define the Executionframe as RvExcFrame for this implementation. - */ -typedef x86ExcFrame ExecutionFrame; - -/** - * @brief Reference the PC register of the execution frame - */ -#define EXECUTION_FRAME_PC(struct) ((struct).eip) - -/** - * @brief Reference the SP register of the execution frame - */ -#define EXECUTION_FRAME_SP(struct) ((struct).esp) - -/** - * @brief Index of SP register in the execution frame. - */ -#define EXECUTION_FRAME_SP_REG (offsetof(x86ExcFrame, esp)/sizeof(uint32_t)) - -/** - * @brief Get register i of the execution frame - */ -#define EXECUTION_FRAME_REG(frame, i) ((frame)->registers[(i)]) - -#endif // _EH_FRAME_PARSER_IMPL_H diff --git a/components/esp_system/test_eh_frame_parser/main.c b/components/esp_system/test_eh_frame_parser/main.c index 40ca64a827..53d45fe843 100644 --- a/components/esp_system/test_eh_frame_parser/main.c +++ b/components/esp_system/test_eh_frame_parser/main.c @@ -5,13 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -/** - * @file DWARF Exception Frames parser header - * - * This file describes the frame types for x86, required for - * parsing `eh_frame` and `eh_frame_hdr`. - */ - #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE #include @@ -22,8 +15,8 @@ #include #include #include -#include "../include/esp_private/eh_frame_parser.h" -#include "eh_frame_parser_impl.h" +#include "esp_private/eh_frame_parser.h" +#include "libunwind.h" /** * @brief Index of x86 registers in `greg_t` structure. @@ -54,6 +47,36 @@ */ #define NUMBER_OF_ITERATION (2 * NUMBER_TO_TEST + 2 + 1) +/** + * @brief Macro for testing calls to libunwind when UNW_ESUCCESS must be returned. + */ +#define UNW_CHECK(call) do { if ((err = (call)) != UNW_ESUCCESS) { \ + printf("\e[31m\e[1mLibunwind error code %d on line %d\e[0m\r\n", err, __LINE__); \ + exit(1); \ + } \ + } while(0) + +/** + * @brief Macro for testing if the given condition is true. To be used with libunwind when + * the result is not necessarily UNW_ESUCCESS. + */ +#define UNW_CHECK_TRUE(cond) do { \ + if (!(cond)) { \ + printf("\e[31m\e[1mLibunwind error on line %d\e[0m\r\n", __LINE__); \ + exit(1); \ + } \ + } while(0) + +/** + * @brief Macro for checking if a PC returned by libunwind is part of the given function + */ +#define UNW_CHECK_PC(pc, funname) do { \ + if (!is_pc_in_function((pc), (funname))) { \ + printf("\e[31m\e[1mPC %04lx should have been of function %s\e[0m\r\n", (pc), (funname)); \ + exit(1); \ + } \ + } while (0) + /** * @brief Define a simple linked list type and initialize one. */ @@ -70,6 +93,10 @@ static struct list_t head = { 0 }; bool is_odd(uint32_t n); bool is_even(uint32_t n); void browse_list(struct list_t* l); +int analyse_callstack(); +int inner_function1(void); +int inner_function2(void); +void test1(void); /** * @brief Structure defining a function of our program. @@ -100,7 +127,27 @@ struct functions_info funs[] = { .name = "is_even", .start = (uintptr_t) &is_even, .end = 0 - } + }, + { + .name = "analyse_callstack", + .start = (uintptr_t) &analyse_callstack, + .end = 0 + }, + { + .name = "inner_function1", + .start = (uintptr_t) &inner_function1, + .end = 0 + }, + { + .name = "inner_function2", + .start = (uintptr_t) &inner_function2, + .end = 0 + }, + { + .name = "test1", + .start = (uintptr_t) &test1, + .end = 0 + }, }; /** @@ -167,7 +214,7 @@ void esp_eh_frame_generated_step(uint32_t pc, uint32_t sp) { /** * @brief Handler called when SIGSEV signal is sent to the program. * - * @param signal Signal received byt the program. Shall be SIGSEGV. + * @param signal Signal received by the program. Shall be SIGSEGV. * @param info Structure containing info about the error itself. Ignored. * @param ucontext Context of the program when the error occurred. This * is used to retrieve the CPU registers value. @@ -269,7 +316,7 @@ bool is_odd(uint32_t n) { } /** - * @brief Initiliaze the global `funs` array. + * @brief Initialize the global `funs` array. */ static inline void initialize_functions_info(void) { @@ -278,9 +325,13 @@ static inline void initialize_functions_info(void) * with the following instructions: * leave (0xc9) * ret (0xc3) + * or + * pop ebp (0x5d) + * ret (0xc3) * Thus, we will look for these instructions. */ uint8_t* instructions = (uint8_t*) funs[i].start; - while (instructions[0] != 0xc9 || instructions[1] != 0xc3) + while ((instructions[0] != 0xc9 || instructions[1] != 0xc3) && + (instructions[0] != 0x5d || instructions[1] != 0xc3) ) instructions++; instructions += 1; funs[i].end = (uintptr_t) instructions; @@ -288,10 +339,9 @@ static inline void initialize_functions_info(void) } /** - * Call the previous functions to create a complex call stack and fail. + * Test the eh_frame_parser for backtracing */ -int main (int argc, char** argv) -{ +void test2(void) { /* Initialize the structure holding information about the signal to override. */ struct sigaction sig = { .sa_mask = 0, @@ -300,18 +350,68 @@ int main (int argc, char** argv) .sa_sigaction = signal_handler }; - /* Look for the functions end functions. */ - initialize_functions_info(); - /* Override default SIGSEV signal callback. */ int res = sigaction(SIGSEGV, &sig, NULL); if (res) { perror("Could not override SIGSEV signal"); - return 1; + exit(1); } /* Trigger the segmentation fault with a complex backtrace. */ is_even(NUMBER_TO_TEST); +} +/** + * Test the libunwind implementation in ESP-IDF + * Let's create some nested function calls to make unwinding more interesting. + * Important: the stack must still be alive when analyzing it, thus it must be done + * within the nested functions. + */ +int analyse_callstack() { + unw_context_t ucp = { 0 }; + unw_cursor_t cur = { 0 }; + unw_word_t pc = 0; + int err = UNW_ESUCCESS; + + UNW_CHECK(unw_getcontext(&ucp)); + UNW_CHECK(unw_init_local(&cur, &ucp)); + UNW_CHECK(unw_get_reg(&cur, UNW_X86_EIP, &pc)); + /* This PC must be inside analyse_callstack */ + UNW_CHECK_PC(pc, "analyse_callstack"); + /* unw_step returns a positive value on success */ + UNW_CHECK_TRUE(unw_step(&cur) > 0); + UNW_CHECK(unw_get_reg(&cur, UNW_X86_EIP, &pc)); + UNW_CHECK_PC(pc, "inner_function2"); + UNW_CHECK_TRUE(unw_step(&cur) > 0); + UNW_CHECK(unw_get_reg(&cur, UNW_X86_EIP, &pc)); + UNW_CHECK_PC(pc, "inner_function1"); + /* unw_step returns if the frame is last one */ + UNW_CHECK_TRUE(unw_step(&cur) >= 0); + UNW_CHECK(unw_get_reg(&cur, UNW_X86_EIP, &pc)); + UNW_CHECK_PC(pc, "test1"); + + return UNW_ESUCCESS; +} + +int __attribute__((noinline)) inner_function2(void) { + return analyse_callstack(); +} + +int __attribute__((noinline)) inner_function1(void) { + return inner_function2(); +} + +void __attribute__((noinline)) test1() { + (void) inner_function1(); +} + +/** + * Call the previous tests within the main. If the first test fails, it will exit by itself. + */ +int main(int argc, char** argv) +{ + initialize_functions_info(); + test1(); + test2(); return 0; } diff --git a/components/esp_system/test_eh_frame_parser/sdkconfig.h b/components/esp_system/test_eh_frame_parser/sdkconfig.h new file mode 100644 index 0000000000..296dcfaf45 --- /dev/null +++ b/components/esp_system/test_eh_frame_parser/sdkconfig.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SDKCONFIG_H +#define SDKCONFIG_H + +#define CONFIG_ESP_SYSTEM_USE_EH_FRAME 1 +#define CONFIG_IDF_TARGET_X86 1 + +#endif // SDKCONFIG_H