diff --git a/Makefile b/Makefile index 105233b62..9dc8a4220 100644 --- a/Makefile +++ b/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) @@ -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)) diff --git a/common/backtrace.c b/common/backtrace.c new file mode 100644 index 000000000..a91d06573 --- /dev/null +++ b/common/backtrace.c @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include + +/* 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); +} diff --git a/docs/user-guide.rst b/docs/user-guide.rst index b8baf4711..5ec932f93 100644 --- a/docs/user-guide.rst +++ b/docs/user-guide.rst @@ -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 diff --git a/include/common/debug.h b/include/common/debug.h index 4c5560f5f..ee25af120 100644 --- a/include/common/debug.h +++ b/include/common/debug.h @@ -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() diff --git a/include/lib/aarch32/arch.h b/include/lib/aarch32/arch.h index 6f0949bb2..f9ed56e29 100644 --- a/include/lib/aarch32/arch.h +++ b/include/lib/aarch32/arch.h @@ -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 ******************************************************************************/ diff --git a/include/lib/aarch32/arch_helpers.h b/include/lib/aarch32/arch_helpers.h index 8eda5cbd0..6369a5d78 100644 --- a/include/lib/aarch32/arch_helpers.h +++ b/include/lib/aarch32/arch_helpers.h @@ -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() diff --git a/include/lib/aarch64/arch_helpers.h b/include/lib/aarch64/arch_helpers.h index 067c374a6..d90061f27 100644 --- a/include/lib/aarch64/arch_helpers.h +++ b/include/lib/aarch64/arch_helpers.h @@ -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