You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
6.6 KiB
256 lines
6.6 KiB
/*
|
|
* Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <arch_helpers.h>
|
|
#include <assert.h>
|
|
#include <console.h>
|
|
#include <debug.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
/* Maximum number of entries in the backtrace to display */
|
|
#define UNWIND_LIMIT 20U
|
|
|
|
/*
|
|
* If -fno-omit-frame-pointer is used:
|
|
*
|
|
* - AArch64: The AAPCS defines the format of the frame records and mandates the
|
|
* usage of r29 as frame pointer.
|
|
*
|
|
* - AArch32: The format of the frame records is not defined in the AAPCS.
|
|
* However, at least GCC and Clang use the same format. When they are forced
|
|
* to only generate A32 code (with -marm), they use r11 as frame pointer and a
|
|
* similar format as in AArch64. If interworking with T32 is enabled, the
|
|
* frame pointer is r7 and the format is different. This is not supported by
|
|
* this implementation of backtrace, so it is needed to use -marm.
|
|
*/
|
|
|
|
/* Frame records form a linked list in the stack */
|
|
struct frame_record {
|
|
/* Previous frame record in the list */
|
|
struct frame_record *parent;
|
|
/* Return address of the function at this level */
|
|
uintptr_t return_addr;
|
|
};
|
|
|
|
static const char *get_el_str(unsigned int el)
|
|
{
|
|
if (el == 3U) {
|
|
return "EL3";
|
|
} else if (el == 2U) {
|
|
return "EL2";
|
|
} else {
|
|
return "S-EL1";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns true if the address points to a virtual address that can be read at
|
|
* the current EL, false otherwise.
|
|
*/
|
|
#ifdef AARCH64
|
|
static bool is_address_readable(uintptr_t addr)
|
|
{
|
|
unsigned int el = get_current_el();
|
|
|
|
if (el == 3U) {
|
|
ats1e3r(addr);
|
|
} else if (el == 2U) {
|
|
ats1e2r(addr);
|
|
} else {
|
|
ats1e1r(addr);
|
|
}
|
|
|
|
isb();
|
|
|
|
/* If PAR.F == 1 the address translation was aborted. */
|
|
if ((read_par_el1() & PAR_F_MASK) != 0U)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
#else /* if AARCH32 */
|
|
static bool is_address_readable(uintptr_t addr)
|
|
{
|
|
unsigned int el = get_current_el();
|
|
|
|
if (el == 3U) {
|
|
write_ats1cpr(addr);
|
|
} else if (el == 2U) {
|
|
write_ats1hr(addr);
|
|
} else {
|
|
write_ats1cpr(addr);
|
|
}
|
|
|
|
isb();
|
|
|
|
/* If PAR.F == 1 the address translation was aborted. */
|
|
if ((read64_par() & PAR_F_MASK) != 0U)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Returns true if all the bytes in a given object are in mapped memory and an
|
|
* LDR using this pointer would succeed, false otherwise.
|
|
*/
|
|
static bool is_valid_object(uintptr_t addr, size_t size)
|
|
{
|
|
assert(size > 0U);
|
|
|
|
if (addr == 0U)
|
|
return false;
|
|
|
|
/* Detect overflows */
|
|
if ((addr + size) < addr)
|
|
return false;
|
|
|
|
/* A pointer not aligned properly could trigger an alignment fault. */
|
|
if ((addr & (sizeof(uintptr_t) - 1U)) != 0U)
|
|
return false;
|
|
|
|
/* Check that all the object is readable */
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (!is_address_readable(addr + i))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Returns true if the specified address is correctly aligned and points to a
|
|
* valid memory region.
|
|
*/
|
|
static bool is_valid_jump_address(uintptr_t addr)
|
|
{
|
|
if (addr == 0U)
|
|
return false;
|
|
|
|
/* Check alignment. Both A64 and A32 use 32-bit opcodes */
|
|
if ((addr & (sizeof(uint32_t) - 1U)) != 0U)
|
|
return false;
|
|
|
|
if (!is_address_readable(addr))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Returns true if the pointer points at a valid frame record, false otherwise.
|
|
*/
|
|
static bool is_valid_frame_record(struct frame_record *fr)
|
|
{
|
|
return is_valid_object((uintptr_t)fr, sizeof(struct frame_record));
|
|
}
|
|
|
|
/*
|
|
* Adjust the frame-pointer-register value by 4 bytes on AArch32 to have the
|
|
* same layout as AArch64.
|
|
*/
|
|
static struct frame_record *adjust_frame_record(struct frame_record *fr)
|
|
{
|
|
#ifdef AARCH64
|
|
return fr;
|
|
#else
|
|
return (struct frame_record *)((uintptr_t)fr - 4U);
|
|
#endif
|
|
}
|
|
|
|
static void unwind_stack(struct frame_record *fr, uintptr_t current_pc,
|
|
uintptr_t link_register)
|
|
{
|
|
uintptr_t call_site;
|
|
static const char *backtrace_str = "%u: %s: 0x%lx\n";
|
|
const char *el_str = get_el_str(get_current_el());
|
|
|
|
if (!is_valid_frame_record(fr)) {
|
|
printf("ERROR: Corrupted frame pointer (frame record address = %p)\n",
|
|
fr);
|
|
return;
|
|
}
|
|
|
|
if (fr->return_addr != link_register) {
|
|
printf("ERROR: Corrupted stack (frame record address = %p)\n",
|
|
fr);
|
|
return;
|
|
}
|
|
|
|
/* The level 0 of the backtrace is the current backtrace function */
|
|
printf(backtrace_str, 0U, el_str, current_pc);
|
|
|
|
/*
|
|
* The last frame record pointer in the linked list at the beginning of
|
|
* the stack should be NULL unless stack is corrupted.
|
|
*/
|
|
for (unsigned int i = 1U; i < UNWIND_LIMIT; i++) {
|
|
/* If an invalid frame record is found, exit. */
|
|
if (!is_valid_frame_record(fr))
|
|
return;
|
|
/*
|
|
* A32 and A64 are fixed length so the address from where the
|
|
* call was made is the instruction before the return address,
|
|
* which is always 4 bytes before it.
|
|
*/
|
|
call_site = fr->return_addr - 4U;
|
|
|
|
/*
|
|
* If the address is invalid it means that the frame record is
|
|
* probably corrupted.
|
|
*/
|
|
if (!is_valid_jump_address(call_site))
|
|
return;
|
|
|
|
printf(backtrace_str, i, el_str, call_site);
|
|
|
|
fr = adjust_frame_record(fr->parent);
|
|
}
|
|
|
|
printf("ERROR: Max backtrace depth reached\n");
|
|
}
|
|
|
|
/*
|
|
* Display a backtrace. The cookie string parameter is displayed along the
|
|
* trace to help filter the log messages.
|
|
*
|
|
* Many things can prevent displaying the expected backtrace. For example,
|
|
* compiler optimizations can use a branch instead of branch with link when it
|
|
* detects a tail call. The backtrace level for this caller will not be
|
|
* displayed, as it does not appear in the call stack anymore. Also, assembly
|
|
* functions will not be displayed unless they setup AAPCS compliant frame
|
|
* records on AArch64 and compliant with GCC-specific frame record format on
|
|
* AArch32.
|
|
*
|
|
* Usage of the trace: addr2line can be used to map the addresses to function
|
|
* and source code location when given the ELF file compiled with debug
|
|
* information. The "-i" flag is highly recommended to improve display of
|
|
* inlined function. The *.dump files generated when buildidng each image can
|
|
* also be used.
|
|
*
|
|
* WARNING: In case of corrupted stack, this function could display security
|
|
* sensitive information past the beginning of the stack so it must not be used
|
|
* in production build. This function is only compiled in when ENABLE_BACKTRACE
|
|
* is set to 1.
|
|
*/
|
|
void backtrace(const char *cookie)
|
|
{
|
|
uintptr_t return_address = (uintptr_t)__builtin_return_address(0U);
|
|
struct frame_record *fr = __builtin_frame_address(0U);
|
|
|
|
/* Printing the backtrace may crash the system, flush before starting */
|
|
(void)console_flush();
|
|
|
|
fr = adjust_frame_record(fr);
|
|
|
|
printf("BACKTRACE: START: %s\n", cookie);
|
|
|
|
unwind_stack(fr, (uintptr_t)&backtrace, return_address);
|
|
|
|
printf("BACKTRACE: END: %s\n", cookie);
|
|
}
|
|
|