arm/backtrace: add support for EHABI(Exception Handling ABI) stack unwinder

Reference:
https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst
https://github.com/ARM-software/abi-aa/releases/download/2022Q1/ehabi32.pdf

Signed-off-by: chao an <anchao@xiaomi.com>
This commit is contained in:
chao an 2022-10-09 12:29:10 +08:00 committed by Xiang Xiao
parent e793207bb6
commit 24129e4ba7
4 changed files with 726 additions and 10 deletions

View File

@ -1218,6 +1218,42 @@ config ARM_SEMIHOSTING_HOSTFS_CACHE_COHERENCE
endif
if SCHED_BACKTRACE
choice
prompt "Choose ARM unwinder"
default UNWINDER_STACK_POINTER if ARM_THUMB
default UNWINDER_FRAME_POINTER if !ARM_THUMB
---help---
This determines which method will be used for unwinding nuttx stack
traces for debug.
config UNWINDER_FRAME_POINTER
bool "Frame pointer unwinder"
select FRAME_POINTER
---help---
This option enables the frame pointer unwinder for unwinding
nuttx stack traces.
config UNWINDER_STACK_POINTER
bool "Stack pointer unwinder"
---help---
This option enables the stack pointer unwinder for unwinding
nuttx stack traces.
config UNWINDER_ARM
bool "ARM EHABI stack unwinder"
---help---
This option enables stack unwinding support in the nuttx
using the information automatically generated by the
compiler. The resulting nuttx image is slightly bigger but
the performance is not affected. Currently, this feature
only works with EABI compilers.
endchoice
endif
if ARCH_ARMV6M
source "arch/arm/src/armv6-m/Kconfig"
endif

View File

@ -63,16 +63,16 @@ ifeq ($(CONFIG_SCHED_THREAD_LOCAL),y)
CMN_CSRCS += arm_tls.c
endif
ifeq ($(CONFIG_SCHED_BACKTRACE),y)
ifeq ($(CONFIG_FRAME_POINTER),y)
ifeq ($(CONFIG_ARM_THUMB),y)
CMN_CSRCS += arm_backtrace_thumb.c
else
CMN_CSRCS += arm_backtrace_fp.c
endif
else
CMN_CSRCS += arm_backtrace_thumb.c
endif
ifeq ($(CONFIG_UNWINDER_FRAME_POINTER),y)
CMN_CSRCS += arm_backtrace_fp.c
endif
ifeq ($(CONFIG_UNWINDER_STACK_POINTER),y)
CMN_CSRCS += arm_backtrace_thumb.c
endif
ifeq ($(CONFIG_UNWINDER_ARM),y)
CMN_CSRCS += arm_backtrace_unwind.c
endif
CMN_ASRCS += vfork.S

View File

@ -74,6 +74,10 @@ ifeq ($(CONFIG_ARM_THUMB),y)
ARCHOPTIMIZATION += -mthumb
endif
ifeq ($(CONFIG_UNWINDER_ARM),y)
ARCHOPTIMIZATION += -funwind-tables -fasynchronous-unwind-tables
endif
# Link Time Optimization
ifeq ($(CONFIG_LTO_THIN),y)

View File

@ -0,0 +1,676 @@
/****************************************************************************
* arch/arm/src/common/arm_backtrace_unwind.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <arch/elf.h>
#include "sched/sched.h"
#include "arm_internal.h"
/****************************************************************************
* Private Types
****************************************************************************/
enum regs
{
#ifdef CONFIG_ARM_THUMB
FP = 7,
#else
FP = 11,
#endif /* CONFIG_ARM_THUMB */
SP = 13,
LR = 14,
PC = 15
};
/****************************************************************************
* Private Data Types
****************************************************************************/
struct unwind_frame_s
{
unsigned long fp;
unsigned long sp;
unsigned long lr;
unsigned long pc;
/* Address of the LR value on the stack */
unsigned long *lr_addr;
/* Highest value of sp allowed */
unsigned long stack_top;
};
struct unwind_ctrl_s
{
unsigned long vrs[16]; /* Virtual register set */
const unsigned long *insn; /* Pointer to the current instructions word */
unsigned long stack_top; /* Highest value of sp allowed */
unsigned long *lr_addr; /* Address of LR value on the stack */
int entries; /* Number of entries left to interpret */
int byte; /* Current byte number in the instructions word */
/* 1 : Check for stack overflow for each register pop.
* 0 : Save overhead if there is plenty of stack remaining.
*/
int check_each_pop;
};
/****************************************************************************
* Private Functions
****************************************************************************/
/* Convert a prel31 symbol to an absolute address */
#define prel31_to_addr(ptr) \
({ \
/* Sign-extend to 32 bits */ \
long offset = (((long)*(ptr)) << 1) >> 1; \
(unsigned long)(ptr) + offset; \
})
/****************************************************************************
* Name: search_index
*
* Description:
* Binary search in the unwind index. The entries are
* guaranteed to be sorted in ascending order by the linker.
*
* start = first entry
* origin = first entry with positive offset
* (or stop if there is no such entry)
* stop - 1 = last entry
*
****************************************************************************/
nosanitize_address
static const struct __EIT_entry *
search_index(unsigned long addr, const struct __EIT_entry *start,
const struct __EIT_entry *origin,
const struct __EIT_entry *stop)
{
unsigned long addr_prel31;
/* Only search in the section with the matching sign. This way the
* prel31 numbers can be compared as unsigned longs.
*/
if (addr < (unsigned long)start)
{
/* Negative offsets: [start; origin) */
stop = origin;
}
else
{
/* Positive offsets: [origin; stop) */
start = origin;
}
/* Prel31 for address relavive to start */
addr_prel31 = (addr - (unsigned long)start) & 0x7fffffff;
while (start < stop - 1)
{
const struct __EIT_entry *mid = start + ((stop - start) >> 1);
/* As addr_prel31 is relative to start an offset is needed to
* make it relative to mid.
*/
if (addr_prel31 -
((unsigned long)mid - (unsigned long)start) < mid->fnoffset)
{
stop = mid;
}
else
{
/* Keep addr_prel31 relative to start */
addr_prel31 -= ((unsigned long)mid - (unsigned long)start);
start = mid;
}
}
return (start->fnoffset <= addr_prel31) ? start : NULL;
}
nosanitize_address
static const struct __EIT_entry *
unwind_find_origin(const struct __EIT_entry *start,
const struct __EIT_entry *stop)
{
const struct __EIT_entry *mid;
while (start < stop)
{
mid = start + ((stop - start) >> 1);
if (mid->fnoffset >= 0x40000000)
{
/* Negative offset */
start = mid + 1;
}
else
{
/* Positive offset */
stop = mid;
}
}
return stop;
}
nosanitize_address
static const struct __EIT_entry *unwind_find_entry(unsigned long addr)
{
/* Main unwind table */
return search_index(addr, __exidx_start,
unwind_find_origin(__exidx_start, __exidx_end),
__exidx_end);
}
nosanitize_address
static unsigned long unwind_get_byte(struct unwind_ctrl_s *ctrl)
{
unsigned long ret;
if (ctrl->entries <= 0)
{
return 0;
}
ret = (*ctrl->insn >> (ctrl->byte * 8)) & 0xff;
if (ctrl->byte == 0)
{
ctrl->insn++;
ctrl->entries--;
ctrl->byte = 3;
}
else
{
ctrl->byte--;
}
return ret;
}
/****************************************************************************
* Name: unwind_pop_register
*
* Description:
* Before poping a register check whether it is feasible or not
*
****************************************************************************/
nosanitize_address
static int unwind_pop_register(struct unwind_ctrl_s *ctrl,
unsigned long **vsp, unsigned int reg)
{
if (ctrl->check_each_pop)
{
if (*vsp >= (unsigned long *)ctrl->stack_top)
{
return -1;
}
}
ctrl->vrs[reg] = *(*vsp);
if (reg == LR)
{
ctrl->lr_addr = *vsp;
}
(*vsp)++;
return 0;
}
/****************************************************************************
* Name: unwind_pop_register
*
* Description:
* Helper functions to execute the instructions
*
****************************************************************************/
nosanitize_address static int
unwind_exec_pop_subset_r4_to_r13(struct unwind_ctrl_s *ctrl,
unsigned long mask)
{
unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
int load_sp;
int reg = 4;
load_sp = mask & (1 << (13 - 4));
while (mask)
{
if ((mask & 1) && unwind_pop_register(ctrl, &vsp, reg))
{
return -1;
}
mask >>= 1;
reg++;
}
if (!load_sp)
{
ctrl->vrs[SP] = (unsigned long)vsp;
}
return 0;
}
nosanitize_address
static int unwind_exec_pop_r4_to_rn(struct unwind_ctrl_s *ctrl,
unsigned long content)
{
unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
int reg;
/* Pop R4-R[4+bbb] */
for (reg = 4; reg <= 4 + (content & 7); reg++)
{
if (unwind_pop_register(ctrl, &vsp, reg))
{
return -1;
}
}
if ((content & 0x8) && unwind_pop_register(ctrl, &vsp, 14))
{
return -1;
}
ctrl->vrs[SP] = (unsigned long)vsp;
return 0;
}
nosanitize_address
static int unwind_exec_pop_subset_r0_to_r3(struct unwind_ctrl_s *ctrl,
unsigned long mask)
{
unsigned long *vsp = (unsigned long *)ctrl->vrs[SP];
int reg = 0;
/* Pop R0-R3 according to mask */
while (mask)
{
if ((mask & 1) && unwind_pop_register(ctrl, &vsp, reg))
{
return -1;
}
mask >>= 1;
reg++;
}
ctrl->vrs[SP] = (unsigned long)vsp;
return 0;
}
/****************************************************************************
* Name: unwind_pop_register
*
* Description:
* Execute the current unwind instruction
*
****************************************************************************/
nosanitize_address
static int unwind_exec_content(struct unwind_ctrl_s *ctrl)
{
unsigned long content = unwind_get_byte(ctrl);
int ret = 0;
if ((content & 0xc0) == 0x00)
{
ctrl->vrs[SP] += ((content & 0x3f) << 2) + 4;
}
else if ((content & 0xc0) == 0x40)
{
ctrl->vrs[SP] -= ((content & 0x3f) << 2) + 4;
}
else if ((content & 0xf0) == 0x80)
{
unsigned long mask;
content = (content << 8) | unwind_get_byte(ctrl);
mask = content & 0x0fff;
ret = (mask == 0) ? -1 :
unwind_exec_pop_subset_r4_to_r13(ctrl, mask);
}
else if ((content & 0xf0) == 0x90 &&
(content & 0x0d) != 0x0d)
{
ctrl->vrs[SP] = ctrl->vrs[content & 0x0f];
}
else if ((content & 0xf0) == 0xa0)
{
ret = unwind_exec_pop_r4_to_rn(ctrl, content);
}
else if (content == 0xb0)
{
if (ctrl->vrs[PC] == 0)
{
ctrl->vrs[PC] = ctrl->vrs[LR];
}
/* No further processing */
ctrl->entries = 0;
}
else if (content == 0xb1)
{
unsigned long mask = unwind_get_byte(ctrl);
if (mask == 0 || mask & 0xf0)
{
ret = -1;
}
else
{
ret = unwind_exec_pop_subset_r0_to_r3(ctrl, mask);
}
}
else if (content == 0xb2)
{
unsigned long uleb128 = unwind_get_byte(ctrl);
ctrl->vrs[SP] += 0x204 + (uleb128 << 2);
}
else
{
ret = -1;
}
return ret;
}
nosanitize_address
int unwind_frame(struct unwind_frame_s *frame)
{
const struct __EIT_entry *entry;
struct unwind_ctrl_s ctrl;
entry = unwind_find_entry(frame->pc);
if (!entry || entry->content == 1)
{
return -1;
}
ctrl.vrs[FP] = frame->fp;
ctrl.vrs[SP] = frame->sp;
ctrl.vrs[LR] = frame->lr;
ctrl.vrs[PC] = 0;
ctrl.stack_top = frame->stack_top;
if (frame->pc == prel31_to_addr(&entry->fnoffset))
{
/* Unwinding is tricky when we're halfway through the prologue,
* since the stack frame that the unwinder expects may not be
* fully set up yet. However, one thing we do know for sure is
* that if we are unwinding from the very first instruction of
* a function, we are still effectively in the stack frame of
* the caller, and the unwind info has no relevance yet.
*/
if (frame->pc == frame->lr)
{
return -1;
}
frame->pc = frame->lr;
return 0;
}
else if ((entry->content & 0x80000000) == 0)
{
/* Prel31 to the unwind table */
ctrl.insn = (unsigned long *)prel31_to_addr(&entry->content);
}
else if ((entry->content & 0xff000000) == 0x80000000)
{
/* Only personality routine 0 supported in the index */
ctrl.insn = &entry->content;
}
else
{
return -1;
}
/* Check the personality routine */
if ((*ctrl.insn & 0xff000000) == 0x80000000)
{
ctrl.byte = 2;
ctrl.entries = 1;
}
else if ((*ctrl.insn & 0xff000000) == 0x81000000)
{
ctrl.byte = 1;
ctrl.entries = 1 + ((*ctrl.insn & 0x00ff0000) >> 16);
}
else
{
return -1;
}
ctrl.check_each_pop = 0;
while (ctrl.entries > 0)
{
int urc;
if ((ctrl.stack_top - ctrl.vrs[SP]) < sizeof(ctrl.vrs))
{
ctrl.check_each_pop = 1;
}
urc = unwind_exec_content(&ctrl);
if (urc < 0)
{
return urc;
}
if (ctrl.vrs[SP] < frame->sp ||
ctrl.vrs[SP] > ctrl.stack_top)
{
return -1;
}
}
if (ctrl.vrs[PC] == 0)
{
ctrl.vrs[PC] = ctrl.vrs[LR];
}
/* Check for infinite loop */
if (frame->pc == ctrl.vrs[PC] && frame->sp == ctrl.vrs[SP])
{
return -1;
}
frame->fp = ctrl.vrs[FP];
frame->sp = ctrl.vrs[SP];
frame->lr = ctrl.vrs[LR];
frame->pc = ctrl.vrs[PC];
frame->lr_addr = ctrl.lr_addr;
return 0;
}
nosanitize_address
static int backtrace_unwind(struct unwind_frame_s *frame,
void **buffer, int size, int *skip)
{
int cnt = 0;
if (frame->pc && cnt < size && (*skip)-- <= 0)
{
buffer[cnt++] = (void *)((frame->pc & ~1) - 2);
}
if (frame->lr && cnt < size && (*skip)-- <= 0)
{
buffer[cnt++] = (void *)((frame->lr & ~1) - 2);
}
while (cnt < size)
{
if (unwind_frame(frame) < 0 || frame->pc == 0)
{
break;
}
if ((*skip)-- <= 0)
{
frame->pc = (frame->pc & ~1) - 2;
if (cnt == 0 || (void *)frame->pc != buffer[cnt - 1])
{
buffer[cnt++] = (void *)frame->pc;
}
}
}
return cnt > 0 ? cnt : 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: up_backtrace
*
* Description:
* up_backtrace() returns a backtrace for the TCB, in the array
* pointed to by buffer. A backtrace is the series of currently active
* function calls for the program. Each item in the array pointed to by
* buffer is of type void *, and is the return address from the
* corresponding stack frame. The size argument specifies the maximum
* number of addresses that can be stored in buffer. If the backtrace is
* larger than size, then the addresses corresponding to the size most
* recent function calls are returned; to obtain the complete backtrace,
* make sure that buffer and size are large enough.
*
* Input Parameters:
* tcb - Address of the task's TCB
* buffer - Return address from the corresponding stack frame
* size - Maximum number of addresses that can be stored in buffer
* skip - number of addresses to be skipped
*
* Returned Value:
* up_backtrace() returns the number of addresses returned in buffer
*
****************************************************************************/
nosanitize_address
int up_backtrace(struct tcb_s *tcb,
void **buffer, int size, int skip)
{
struct tcb_s *rtcb = running_task();
struct unwind_frame_s frame;
irqstate_t flags;
int ret;
if (size <= 0 || !buffer)
{
return 0;
}
if (tcb == NULL || tcb == rtcb)
{
frame.fp = (unsigned long)__builtin_frame_address(0);
frame.lr = (unsigned long)__builtin_return_address(0);
frame.pc = (unsigned long)&up_backtrace;
frame.sp = frame.fp;
frame.stack_top = (unsigned long)rtcb->stack_base_ptr +
rtcb->adj_stack_size;
if (up_interrupt_context())
{
#if CONFIG_ARCH_INTERRUPTSTACK > 7
# ifdef CONFIG_SMP
frame.stack_top = arm_intstack_top();
# else
frame.stack_top = &g_intstacktop;
# endif /* CONFIG_SMP */
#endif /* CONFIG_ARCH_INTERRUPTSTACK > 7 */
ret = backtrace_unwind(&frame, buffer, size, &skip);
if (ret < size)
{
frame.fp = rtcb->xcp.regs[REG_FP];
frame.sp = rtcb->xcp.regs[REG_SP];
frame.pc = rtcb->xcp.regs[REG_PC];
frame.lr = 0;
frame.stack_top = (unsigned long)rtcb->stack_base_ptr +
rtcb->adj_stack_size;
ret += backtrace_unwind(&frame, &buffer[ret],
size - ret, &skip);
}
}
else
{
ret = backtrace_unwind(&frame, buffer, size, &skip);
}
}
else
{
flags = enter_critical_section();
frame.fp = tcb->xcp.regs[REG_FP];
frame.sp = tcb->xcp.regs[REG_SP];
frame.lr = tcb->xcp.regs[REG_LR];
frame.pc = tcb->xcp.regs[REG_PC];
frame.stack_top = (unsigned long)tcb->stack_base_ptr +
tcb->adj_stack_size;
ret = backtrace_unwind(&frame, buffer, size, &skip);
leave_critical_section(flags);
}
return ret;
}