mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-31 21:14:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			430 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| 
 | |
| /*
 | |
|  * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
 | |
|  *
 | |
|  * SPDX-License-Identifier: Apache-2.0
 | |
|  */
 | |
| 
 | |
| #define _POSIX_C_SOURCE 200809L
 | |
| #define _DEFAULT_SOURCE
 | |
| #include <stdio.h>
 | |
| #include <signal.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdbool.h>
 | |
| #include <assert.h>
 | |
| #include <ucontext.h>
 | |
| #include "esp_private/eh_frame_parser.h"
 | |
| #include "libunwind.h"
 | |
| 
 | |
| /**
 | |
|  * @brief Index of x86 registers in `greg_t` structure.
 | |
|  */
 | |
| #define REG_EDI     4
 | |
| #define REG_ESI     5
 | |
| #define REG_EBP     6
 | |
| #define REG_ESP     7
 | |
| #define REG_EBX     8
 | |
| #define REG_EDX     9
 | |
| #define REG_ECX     10
 | |
| #define REG_EAX     11
 | |
| #define REG_EIP     14
 | |
| 
 | |
| /**
 | |
|  * @brief Number of functions in the funs structure described below.
 | |
|  */
 | |
| #define FUNCTIONS_COUNT ((sizeof(funs)/sizeof(*funs)))
 | |
| 
 | |
| /**
 | |
|  * @brief Number which will determine the depth of the call stack.
 | |
|  * Check `main()` for more information.
 | |
|  */
 | |
| #define NUMBER_TO_TEST          (4)
 | |
| 
 | |
| /**
 | |
|  * @brief Number of iteration for function `esp_eh_frame_generated_step`.
 | |
|  */
 | |
| #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.
 | |
|  */
 | |
| struct list_t {
 | |
|     uint32_t value;
 | |
|     struct list_t *next;
 | |
| };
 | |
| 
 | |
| static struct list_t head = { 0 };
 | |
| /**
 | |
|  * Few recursive functions to make the the call stack a bit more complex than a
 | |
|  * single function call would give.
 | |
|  */
 | |
| 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.
 | |
|  * This will be used to translate the backtrace.
 | |
|  */
 | |
| struct functions_info {
 | |
|     const char* name;
 | |
|     uintptr_t start;
 | |
|     uintptr_t end;  /* will be filled at runtime */
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @brief Structure storing the information about the
 | |
|  * function that will be part of the backtrace.
 | |
|  */
 | |
| struct functions_info funs[] = {
 | |
|     {
 | |
|         .name = "browse_list",
 | |
|         .start = (uintptr_t) &browse_list,
 | |
|         .end = 0
 | |
|     },
 | |
|     {
 | |
|         .name = "is_odd",
 | |
|         .start = (uintptr_t) &is_odd,
 | |
|         .end = 0
 | |
|     },
 | |
|     {
 | |
|         .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
 | |
|     },
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @brief Test whether the address passed as PC is part of the function which
 | |
|  * name is `function_name`. The global array `funs` is used.
 | |
|  *
 | |
|  * @param pc Program counter to test. (address in the program)
 | |
|  * @param function_name Function name to check the address of.
 | |
|  *
 | |
|  * @return true if PC is in the function called `function_name`, false else.
 | |
|  */
 | |
| bool is_pc_in_function(const uint32_t pc, const char* function_name)
 | |
| {
 | |
|     for (uint32_t i = 0; i < FUNCTIONS_COUNT; i++) {
 | |
|         const struct functions_info current_fun = funs[i];
 | |
|         if (strcmp(current_fun.name, function_name) == 0) {
 | |
|             return current_fun.start <= pc && pc <= current_fun.end;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Function not found. */
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Number of times `esp_eh_frame_generated_step` is called.
 | |
|  */
 | |
| static uint32_t iteration = 1;
 | |
| 
 | |
| /**
 | |
|  * @brief Override the default function called when a backtrace step is
 | |
|  * generated.
 | |
|  */
 | |
| void esp_eh_frame_generated_step(uint32_t pc, uint32_t sp)
 | |
| {
 | |
|     /* The first PCs in the backtrace are calls to `browse_list()` + 2.
 | |
|      * This is due to the fact that the list contains all the numbers
 | |
|      * between NUMBER_TO_TEST to 0 included. Moreover, another call
 | |
|      * is made when we meet the NULL pointer. */
 | |
|     if (iteration > 0 && iteration <= (NUMBER_TO_TEST + 2)) {
 | |
|         assert(is_pc_in_function(pc, "browse_list"));
 | |
|     } else {
 | |
|         /**
 | |
|          * The backtrace should be:
 | |
|          * - in is_odd when iteration is even.
 | |
|          * - in is_even when iteration is odd.
 | |
|          *
 | |
|          * The backtrace finishes when the iteration reaches the end of
 | |
|          * browse_list (NUMBER_TO_TEST + 2 iterations), is_even
 | |
|          * (NUMBER_TO_TEST/2 calls) and is_odd (NUMBER_TO_TEST/2 calls) calls.
 | |
|          */
 | |
|         if (iteration >= NUMBER_OF_ITERATION) {
 | |
|             return;
 | |
|         } else if (iteration % 2 == 0) {
 | |
|             assert(is_pc_in_function(pc, "is_odd"));
 | |
|         } else {
 | |
|             assert(is_pc_in_function(pc, "is_even"));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Number of times this function has been entered. */
 | |
|     iteration++;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Handler called when SIGSEV signal is sent to the program.
 | |
|  *
 | |
|  * @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.
 | |
|  */
 | |
| void signal_handler(int signal, siginfo_t *info, void *ucontext)
 | |
| {
 | |
|     /* Setup the execution frame as expected by the eh_frame_parser.
 | |
|      * Indeed, the registers index defined in ucontext.h are NOT the same
 | |
|      * the registers index DWARF is expecting. */
 | |
|     ucontext_t* context = (ucontext_t*) ucontext;
 | |
|     greg_t *gregset = context->uc_mcontext.gregs;
 | |
|     x86ExcFrame frame = {
 | |
|         .eax = gregset[REG_EAX],
 | |
|         .ecx = gregset[REG_ECX],
 | |
|         .edx = gregset[REG_EDX],
 | |
|         .ebx = gregset[REG_EBX],
 | |
|         .esp = gregset[REG_ESP],
 | |
|         .ebp = gregset[REG_EBP],
 | |
|         .esi = gregset[REG_ESI],
 | |
|         .edi = gregset[REG_EDI],
 | |
|         .eip = gregset[REG_EIP]
 | |
|     };
 | |
| 
 | |
|     /* The following function will use panic_print_str and panic_print_hex
 | |
|      * function to output the data.
 | |
|      * Instead of replacing stdout file descriptor with a pipe, we can simply
 | |
|      * replace these functions to store the data instead of printing them.
 | |
|      */
 | |
|     esp_eh_frame_print_backtrace(&frame);
 | |
| 
 | |
|     /* No assert has been triggered, the backtrace succeeded if the number of
 | |
|      * iterations of function `esp_eh_frame_generated_step` is correct. */
 | |
|     if (iteration == NUMBER_OF_ITERATION) {
 | |
|         printf("\e[32m\e[1mAll tests passed \e[0m\r\n");
 | |
|     } else {
 | |
|         printf("\e[31m\e[1mWrong length of backtrace (%d iteration, expected %d) \e[0m\r\n",
 | |
|                iteration, NUMBER_OF_ITERATION);
 | |
|         exit(1);
 | |
|     }
 | |
| 
 | |
|     /* Everything went fine, exit normally. */
 | |
|     exit(0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Browse the list passed as an argument.
 | |
|  * The following function will trigger a SIGSEV signal on purpose, in order to
 | |
|  * generate the backtrace.
 | |
|  *
 | |
|  * @param l List to browse.
 | |
|  */
 | |
| void browse_list(struct list_t* l)
 | |
| {
 | |
|     browse_list(l->next);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Add a number to the global list `head`.
 | |
|  *
 | |
|  * @param n Number to add in the list.
 | |
|  */
 | |
| void add_number_to_list(uint32_t n)
 | |
| {
 | |
|     struct list_t* l = malloc(sizeof(struct list_t));
 | |
|     l->value = n;
 | |
|     l->next = head.next;
 | |
|     head.next = l;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Test if the number passed is even.
 | |
|  * This function will fail, on purpose.
 | |
|  *
 | |
|  * @param n Number to test.
 | |
|  *
 | |
|  * @return true if even, false else.
 | |
|  */
 | |
| bool is_even(uint32_t n)
 | |
| {
 | |
|     add_number_to_list(n);
 | |
|     if (n == 0) {
 | |
|         browse_list(head.next);
 | |
|         return true;
 | |
|     }
 | |
|     return is_odd(n - 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Test if the number passed is odd.
 | |
|  * This function will fail, on purpose.
 | |
|  *
 | |
|  * @param n Number to test.
 | |
|  *
 | |
|  * @return true if odd, false else.
 | |
|  */
 | |
| bool is_odd(uint32_t n)
 | |
| {
 | |
|     add_number_to_list(n);
 | |
|     if (n == 0) {
 | |
|         browse_list(head.next);
 | |
|         return false;
 | |
|     }
 | |
|     return is_even(n - 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * @brief Initialize the global `funs` array.
 | |
|  */
 | |
| static inline void initialize_functions_info(void)
 | |
| {
 | |
|     for (uint32_t i = 0; i < FUNCTIONS_COUNT; i++) {
 | |
|         /* Each of the functions defined in this structure finishes
 | |
|          * 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) &&
 | |
|                 (instructions[0] != 0x5d || instructions[1] != 0xc3)) {
 | |
|             instructions++;
 | |
|         }
 | |
|         instructions += 1;
 | |
|         funs[i].end = (uintptr_t) instructions;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Test the eh_frame_parser for backtracing
 | |
|  */
 | |
| void test2(void)
 | |
| {
 | |
|     /* Initialize the structure holding information about the signal to override. */
 | |
|     struct sigaction sig = {
 | |
|         .sa_mask = 0,
 | |
|         .sa_flags = SA_SIGINFO,
 | |
|         .sa_restorer = NULL,
 | |
|         .sa_sigaction = signal_handler
 | |
|     };
 | |
| 
 | |
|     /* Override default SIGSEV signal callback. */
 | |
|     int res = sigaction(SIGSEGV, &sig, NULL);
 | |
|     if (res) {
 | |
|         perror("Could not override SIGSEV signal");
 | |
|         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;
 | |
| }
 | 
