diff --git a/Makefile b/Makefile index 74d5180..c79264b 100644 --- a/Makefile +++ b/Makefile @@ -108,13 +108,6 @@ 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) @@ -206,11 +199,6 @@ GCC_V_OUTPUT := $(shell $(CC) -v 2>&1) -# 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)) @@ -238,10 +226,6 @@ 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 \ @@ -270,6 +254,8 @@ ${SPD_INCLUDES} \ -Iinclude/tools_share +include common/backtrace/backtrace.mk + ################################################################################ # Generic definitions ################################################################################ @@ -369,15 +355,6 @@ # 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 \ @@ -568,7 +545,6 @@ $(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_PIE)) $(eval $(call assert_boolean,ENABLE_PMF)) @@ -619,7 +595,6 @@ $(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_PIE)) $(eval $(call add_define,ENABLE_PMF)) diff --git a/common/backtrace.c b/common/backtrace.c deleted file mode 100644 index a91d065..0000000 --- a/common/backtrace.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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/common/backtrace/backtrace.c b/common/backtrace/backtrace.c new file mode 100644 index 0000000..a91d065 --- /dev/null +++ b/common/backtrace/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/common/backtrace/backtrace.mk b/common/backtrace/backtrace.mk new file mode 100644 index 0000000..e669331 --- /dev/null +++ b/common/backtrace/backtrace.mk @@ -0,0 +1,31 @@ +# +# Copyright (c) 2018, ARM Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +# Enable backtrace by default in DEBUG AArch64 builds +ifeq (${ARCH},aarch32) + ENABLE_BACKTRACE := 0 +else + ENABLE_BACKTRACE := ${DEBUG} +endif + +ifeq (${ENABLE_BACKTRACE},1) + # Force the compiler to include the frame pointer + TF_CFLAGS += -fno-omit-frame-pointer + + BL_COMMON_SOURCES += common/backtrace/backtrace.c +endif + +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 + +$(eval $(call assert_boolean,ENABLE_BACKTRACE)) +$(eval $(call add_define,ENABLE_BACKTRACE))