Merge pull request #1535 from antonio-nino-diaz-arm/an/backtrace
Introduce backtrace function
This commit is contained in:
commit
fe73b70470
35
Makefile
35
Makefile
|
@ -100,6 +100,13 @@ else
|
|||
LOG_LEVEL := 20
|
||||
endif
|
||||
|
||||
# Enable backtrace by default in DEBUG AArch64 builds
|
||||
ifeq (${ARCH},aarch32)
|
||||
ENABLE_BACKTRACE := 0
|
||||
else
|
||||
ENABLE_BACKTRACE := ${DEBUG}
|
||||
endif
|
||||
|
||||
# Default build string (git branch and commit)
|
||||
ifeq (${BUILD_STRING},)
|
||||
BUILD_STRING := $(shell git describe --always --dirty --tags 2> /dev/null)
|
||||
|
@ -166,6 +173,14 @@ TF_CFLAGS_aarch64 = -march=armv8-a
|
|||
LD = $(LINKER)
|
||||
endif
|
||||
|
||||
ifeq (${AARCH32_INSTRUCTION_SET},A32)
|
||||
TF_CFLAGS_aarch32 += -marm
|
||||
else ifeq (${AARCH32_INSTRUCTION_SET},T32)
|
||||
TF_CFLAGS_aarch32 += -mthumb
|
||||
else
|
||||
$(error Error: Unknown AArch32 instruction set ${AARCH32_INSTRUCTION_SET})
|
||||
endif
|
||||
|
||||
TF_CFLAGS_aarch32 += -mno-unaligned-access
|
||||
TF_CFLAGS_aarch64 += -mgeneral-regs-only -mstrict-align
|
||||
|
||||
|
@ -188,6 +203,11 @@ ifneq ($(PIE_FOUND),)
|
|||
TF_CFLAGS += -fno-PIE
|
||||
endif
|
||||
|
||||
# Force the compiler to include the frame pointer
|
||||
ifeq (${ENABLE_BACKTRACE},1)
|
||||
TF_CFLAGS += -fno-omit-frame-pointer
|
||||
endif
|
||||
|
||||
TF_LDFLAGS += --fatal-warnings -O1
|
||||
TF_LDFLAGS += --gc-sections
|
||||
TF_LDFLAGS += $(TF_LDFLAGS_$(ARCH))
|
||||
|
@ -215,6 +235,10 @@ ifeq ($(notdir $(CC)),armclang)
|
|||
BL_COMMON_SOURCES += lib/${ARCH}/armclang_printf.S
|
||||
endif
|
||||
|
||||
ifeq (${ENABLE_BACKTRACE},1)
|
||||
BL_COMMON_SOURCES += common/backtrace.c
|
||||
endif
|
||||
|
||||
INCLUDES += -Iinclude \
|
||||
-Iinclude/bl1 \
|
||||
-Iinclude/bl2 \
|
||||
|
@ -345,6 +369,15 @@ endif
|
|||
# Check incompatible options
|
||||
################################################################################
|
||||
|
||||
ifeq (${ARCH},aarch32)
|
||||
ifeq (${ENABLE_BACKTRACE},1)
|
||||
ifneq (${AARCH32_INSTRUCTION_SET},A32)
|
||||
$(error Error: AARCH32_INSTRUCTION_SET=A32 is needed \
|
||||
for ENABLE_BACKTRACE when compiling for AArch32.)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef EL3_PAYLOAD_BASE
|
||||
ifdef PRELOADED_BL33_BASE
|
||||
$(warning "PRELOADED_BL33_BASE and EL3_PAYLOAD_BASE are \
|
||||
|
@ -551,6 +584,7 @@ $(eval $(call assert_boolean,DYN_DISABLE_AUTH))
|
|||
$(eval $(call assert_boolean,EL3_EXCEPTION_HANDLING))
|
||||
$(eval $(call assert_boolean,ENABLE_AMU))
|
||||
$(eval $(call assert_boolean,ENABLE_ASSERTIONS))
|
||||
$(eval $(call assert_boolean,ENABLE_BACKTRACE))
|
||||
$(eval $(call assert_boolean,ENABLE_MPAM_FOR_LOWER_ELS))
|
||||
$(eval $(call assert_boolean,ENABLE_PLAT_COMPAT))
|
||||
$(eval $(call assert_boolean,ENABLE_PMF))
|
||||
|
@ -603,6 +637,7 @@ $(eval $(call add_define,CTX_INCLUDE_FPREGS))
|
|||
$(eval $(call add_define,EL3_EXCEPTION_HANDLING))
|
||||
$(eval $(call add_define,ENABLE_AMU))
|
||||
$(eval $(call add_define,ENABLE_ASSERTIONS))
|
||||
$(eval $(call add_define,ENABLE_BACKTRACE))
|
||||
$(eval $(call add_define,ENABLE_MPAM_FOR_LOWER_ELS))
|
||||
$(eval $(call add_define,ENABLE_PLAT_COMPAT))
|
||||
$(eval $(call add_define,ENABLE_PMF))
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
|
@ -75,7 +75,7 @@ In addition, the following optional packages and tools may be needed:
|
|||
|
||||
- To create and modify the diagram files included in the documentation, `Dia`_.
|
||||
This tool can be found in most Linux distributions. Inkscape is needed to
|
||||
generate the actual *.png files.
|
||||
generate the actual \*.png files.
|
||||
|
||||
Getting the TF-A source code
|
||||
----------------------------
|
||||
|
@ -212,6 +212,10 @@ performed.
|
|||
Common build options
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- ``AARCH32_INSTRUCTION_SET``: Choose the AArch32 instruction set that the
|
||||
compiler should use. Valid values are T32 and A32. It defaults to T32 due to
|
||||
code having a smaller resulting size.
|
||||
|
||||
- ``AARCH32_SP`` : Choose the AArch32 Secure Payload component to be built as
|
||||
as the BL32 image when ``ARCH=aarch32``. The value should be the path to the
|
||||
directory containing the SP source, relative to the ``bl32/``; the directory
|
||||
|
@ -351,6 +355,16 @@ Common build options
|
|||
that is only required for the assertion and does not fit in the assertion
|
||||
itself.
|
||||
|
||||
- ``ENABLE_BACKTRACE``: This option controls whether to enables backtrace
|
||||
dumps or not. It is supported in both AArch64 and AArch32. However, in
|
||||
AArch32 the format of the frame records are not defined in the AAPCS and they
|
||||
are defined by the implementation. This implementation of backtrace only
|
||||
supports the format used by GCC when T32 interworking is disabled. For this
|
||||
reason enabling this option in AArch32 will force the compiler to only
|
||||
generate A32 code. This option is enabled by default only in AArch64 debug
|
||||
builds, but this behaviour can be overriden in each platform's Makefile or in
|
||||
the build command line.
|
||||
|
||||
- ``ENABLE_MPAM_FOR_LOWER_ELS``: Boolean option to enable lower ELs to use MPAM
|
||||
feature. MPAM is an optional Armv8.4 extension that enables various memory
|
||||
system components and resources to define partitions; software running at
|
||||
|
|
|
@ -27,7 +27,9 @@
|
|||
|
||||
#ifndef __ASSEMBLY__
|
||||
#include <cdefs.h>
|
||||
#include <console.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
|
@ -83,8 +85,20 @@
|
|||
# define VERBOSE(...) no_tf_log(LOG_MARKER_VERBOSE __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#if ENABLE_BACKTRACE
|
||||
void backtrace(const char *cookie);
|
||||
#else
|
||||
#define backtrace(x)
|
||||
#endif
|
||||
|
||||
void __dead2 do_panic(void);
|
||||
#define panic() do_panic()
|
||||
|
||||
#define panic() \
|
||||
do { \
|
||||
backtrace(__func__); \
|
||||
(void)console_flush(); \
|
||||
do_panic(); \
|
||||
} while (false)
|
||||
|
||||
/* Function called when stack protection check code detects a corrupted stack */
|
||||
void __dead2 __stack_chk_fail(void);
|
||||
|
|
|
@ -473,6 +473,8 @@
|
|||
#define CCSIDR p15, 1, c0, c0, 0
|
||||
#define HTCR p15, 4, c2, c0, 2
|
||||
#define HMAIR0 p15, 4, c10, c2, 0
|
||||
#define ATS1CPR p15, 0, c7, c8, 0
|
||||
#define ATS1HR p15, 4, c7, c8, 0
|
||||
#define DBGOSDLR p14, 0, c1, c3, 4
|
||||
|
||||
/* Debug register defines. The format is: coproc, opt1, CRn, CRm, opt2 */
|
||||
|
@ -513,6 +515,7 @@
|
|||
#define VTTBR_64 p15, 6, c2
|
||||
#define CNTPCT_64 p15, 0, c14
|
||||
#define HTTBR_64 p15, 4, c2
|
||||
#define PAR_64 p15, 0, c7
|
||||
|
||||
/* 64 bit GICv3 CPU Interface system register defines. The format is: coproc, opt1, CRm */
|
||||
#define ICC_SGI1R_EL1_64 p15, 0, c12
|
||||
|
@ -569,6 +572,12 @@
|
|||
#define MAKE_MAIR_NORMAL_MEMORY(inner, outer) \
|
||||
((inner) | ((outer) << MAIR_NORM_OUTER_SHIFT))
|
||||
|
||||
/* PAR fields */
|
||||
#define PAR_F_SHIFT U(0)
|
||||
#define PAR_F_MASK ULL(0x1)
|
||||
#define PAR_ADDR_SHIFT U(12)
|
||||
#define PAR_ADDR_MASK (BIT(40) - ULL(1)) /* 40-bits-wide page address */
|
||||
|
||||
/*******************************************************************************
|
||||
* Definitions for system register interface to AMU for ARMv8.4 onwards
|
||||
******************************************************************************/
|
||||
|
|
|
@ -276,6 +276,10 @@ DEFINE_COPROCR_RW_FUNCS(hdcr, HDCR)
|
|||
DEFINE_COPROCR_RW_FUNCS(cnthp_ctl, CNTHP_CTL)
|
||||
DEFINE_COPROCR_READ_FUNC(pmcr, PMCR)
|
||||
|
||||
DEFINE_COPROCR_RW_FUNCS(ats1cpr, ATS1CPR)
|
||||
DEFINE_COPROCR_RW_FUNCS(ats1hr, ATS1HR)
|
||||
DEFINE_COPROCR_RW_FUNCS_64(par, PAR_64)
|
||||
|
||||
DEFINE_COPROCR_RW_FUNCS(nsacr, NSACR)
|
||||
|
||||
/* AArch32 coproc registers for 32bit MMU descriptor support */
|
||||
|
@ -333,6 +337,17 @@ DEFINE_DCOP_PARAM_FUNC(cvac, DCCMVAC)
|
|||
((GET_M32(read_cpsr()) == MODE32_mon) || \
|
||||
(IS_IN_SECURE() && (GET_M32(read_cpsr()) != MODE32_usr)))
|
||||
|
||||
static inline unsigned int get_current_el(void)
|
||||
{
|
||||
if (IS_IN_EL3()) {
|
||||
return 3U;
|
||||
} else if (IS_IN_EL2()) {
|
||||
return 2U;
|
||||
} else {
|
||||
return 1U;
|
||||
}
|
||||
}
|
||||
|
||||
/* Macros for compatibility with AArch64 system registers */
|
||||
#define read_mpidr_el1() read_mpidr()
|
||||
|
||||
|
|
|
@ -155,7 +155,9 @@ DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s12e1r)
|
|||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s12e1w)
|
||||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s12e0r)
|
||||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s12e0w)
|
||||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s1e1r)
|
||||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s1e2r)
|
||||
DEFINE_SYSOP_TYPE_PARAM_FUNC(at, s1e3r)
|
||||
|
||||
void flush_dcache_range(uintptr_t addr, size_t size);
|
||||
void clean_dcache_range(uintptr_t addr, size_t size);
|
||||
|
@ -353,6 +355,12 @@ DEFINE_RENAME_SYSREG_READ_FUNC(erxmisc1_el1, ERXMISC1_EL1)
|
|||
|
||||
#define IS_IN_EL1() IS_IN_EL(1)
|
||||
#define IS_IN_EL3() IS_IN_EL(3)
|
||||
#define IS_IN_EL3() IS_IN_EL(3)
|
||||
|
||||
static inline unsigned int get_current_el(void)
|
||||
{
|
||||
return GET_EL(read_CurrentEl());
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if an EL is implemented from AA64PFR0 register fields. 'el' argument
|
||||
|
|
|
@ -20,19 +20,23 @@
|
|||
void __assert(const char *file, unsigned int line, const char *assertion)
|
||||
{
|
||||
printf("ASSERT: %s:%d:%s\n", file, line, assertion);
|
||||
console_flush();
|
||||
backtrace("assert");
|
||||
(void)console_flush();
|
||||
plat_panic_handler();
|
||||
}
|
||||
#elif PLAT_LOG_LEVEL_ASSERT >= LOG_LEVEL_INFO
|
||||
void __assert(const char *file, unsigned int line)
|
||||
{
|
||||
printf("ASSERT: %s:%d\n", file, line);
|
||||
console_flush();
|
||||
backtrace("assert");
|
||||
(void)console_flush();
|
||||
plat_panic_handler();
|
||||
}
|
||||
#else
|
||||
void __assert(void)
|
||||
{
|
||||
backtrace("assert");
|
||||
(void)console_flush();
|
||||
plat_panic_handler();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
# poised to handle dependencies, as all build variables would have a default
|
||||
# value by then.
|
||||
|
||||
# Use T32 by default
|
||||
AARCH32_INSTRUCTION_SET := T32
|
||||
|
||||
# The AArch32 Secure Payload to be built as BL32 image
|
||||
AARCH32_SP := none
|
||||
|
||||
|
|
Loading…
Reference in New Issue