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