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.
This commit is contained in:
Omar Chebib 2022-05-16 18:27:45 +08:00
parent 1438d9a02c
commit eeaa40f71d
10 changed files with 760 additions and 187 deletions

View File

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

View File

@ -24,4 +24,5 @@ void esp_eh_frame_print_backtrace(const void *frame_or);
}
#endif
#endif
#endif // EH_FRAME_PARSER_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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