Skip to content

Commit

Permalink
System: implement libunwind library for RISC-V backtracing
Browse files Browse the repository at this point in the history
Closes #7866

A minimal x86 implementation has also been added, it is used to perform a host test.
  • Loading branch information
o-marshmallow committed May 15, 2023
1 parent 1438d9a commit eeaa40f
Show file tree
Hide file tree
Showing 10 changed files with 760 additions and 187 deletions.
132 changes: 128 additions & 4 deletions components/esp_system/eh_frame_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion components/esp_system/include/esp_private/eh_frame_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ void esp_eh_frame_print_backtrace(const void *frame_or);
}
#endif

#endif

#endif // EH_FRAME_PARSER_H
135 changes: 135 additions & 0 deletions components/esp_system/include/libunwind.h
Original file line number Diff line number Diff line change
@@ -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
64 changes: 0 additions & 64 deletions components/esp_system/port/include/riscv/eh_frame_parser_impl.h

This file was deleted.

Loading

0 comments on commit eeaa40f

Please sign in to comment.