浏览代码

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.
Omar Chebib 3 年之前
父节点
当前提交
eeaa40f71d

+ 128 - 4
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 <string.h>
 
 #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

+ 2 - 1
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

+ 135 - 0
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 <stddef.h>
+#include <stdint.h>
+
+#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

+ 0 - 64
components/esp_system/port/include/riscv/eh_frame_parser_impl.h

@@ -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

+ 213 - 0
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 <stddef.h>
+#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

+ 146 - 0
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 <stdint.h>
+#include <stddef.h>
+
+#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

+ 4 - 15
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

+ 0 - 83
components/esp_system/test_eh_frame_parser/eh_frame_parser_impl.h

@@ -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 <stdint.h>
-#include <stddef.h>
-
-/**
- * @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

+ 120 - 20
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 <stdio.h>
@@ -22,8 +15,8 @@
 #include <stdbool.h>
 #include <assert.h>
 #include <ucontext.h>
-#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;
 }

+ 12 - 0
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