backtrace: Introduce backtrace function

This function diplays the backtrace, the current EL and security state
to allow a post-processing tool to choose the right binary to interpret
the dump.

The output can be fed to GNU addr2line to resolve function names given
an ELF binary compiled with debug information. The "-i" flag is
recommended to improve display in case of inlined functions. The *.dump
files generated during the build process can also be used.

The function works in AArch64 and AArch32. In AArch32 it only works in
A32 mode (without T32 interworking), which is enforced in the Makefile.

Sample output of a backtrace at EL3:

    BACKTRACE: START: function_name
    0: EL3: 0x798
    1: EL3: 0x538
    2: EL3: 0x550
    3: EL3: 0x55c
    4: EL3: 0x568
    5: EL3: 0x5a8
    6: EL3: 0xf4
    BACKTRACE: END: function_name

In order to enable it the new option ENABLE_BACKTRACE must be set to 1.
This option is set to 1 by default only in AArch64 debug builds. As
usual, it can be overridden by the platform makefile and in the build
command line.

Change-Id: Icaff39b0e5188329728be2f3c72b868b2368e794
Co-authored-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
Signed-off-by: Antonio Nino Diaz <antonio.ninodiaz@arm.com>
Signed-off-by: Douglas Raillard <douglas.raillard@arm.com>
This commit is contained in:
Douglas Raillard 2018-08-21 12:54:45 +01:00 committed by Antonio Nino Diaz
parent 8fd9d4d58a
commit 0c62883f7e
7 changed files with 331 additions and 0 deletions

View File

@ -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)
@ -196,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))
@ -223,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 \
@ -353,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 \
@ -559,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))
@ -611,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))

256
common/backtrace.c Normal file
View File

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

View File

@ -355,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

View File

@ -83,6 +83,12 @@
# 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()

View File

@ -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
******************************************************************************/

View File

@ -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()

View File

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