Error handling _在FreeRTOS上展开不同上下文的\u回溯
您好,我正在尝试在FreeRTOS项目中实现错误处理。看门狗复位前,看门狗中断触发处理程序。其思想是记录失败任务的任务名+调用堆栈。Error handling _在FreeRTOS上展开不同上下文的\u回溯,error-handling,embedded,freertos,libunwind,Error Handling,Embedded,Freertos,Libunwind,您好,我正在尝试在FreeRTOS项目中实现错误处理。看门狗复位前,看门狗中断触发处理程序。其思想是记录失败任务的任务名+调用堆栈。 我已经设法回溯调用堆栈,但是在错误的上下文中,中断的上下文。而我需要存储在pxCurrentTCB中的失败任务的上下文。但我不知道如何告诉_Unwind_Backtrace使用它而不是中断上下文,它是从哪里调用的。 所以我想_Unwind _Backtrace不是它调用的上下文,而是针对pxCurrentTCB中的不同上下文。我已经搜索并试图了解“放松”回溯是如何
我已经设法回溯调用堆栈,但是在错误的上下文中,中断的上下文。而我需要存储在pxCurrentTCB中的失败任务的上下文。但我不知道如何告诉_Unwind_Backtrace使用它而不是中断上下文,它是从哪里调用的。 所以我想_Unwind _Backtrace不是它调用的上下文,而是针对pxCurrentTCB中的不同上下文。我已经搜索并试图了解“放松”回溯是如何工作的,但没有成功,所以请帮助。
如有任何帮助,尤其是示例代码,将不胜感激。多谢各位
_Unwind_Reason_Code unwind_backtrace_callback(_Unwind_Context * context, void * arg)
{
static uint8_t row = 1;
char str_buff[BUFF_SIZE];
uintptr_t pc = _Unwind_GetIP(context);
if (pc && row < MAX_ROW) {
snprintf(str_buff, sizeof(str_buff), "%d .. 0x%x", row, pc);
printString(str_buff, 0, ROW_SIZE * row++);
}
return _URC_NO_REASON;
}
void WDOG1_DriverIRQHandler(void)
{
printString(pxCurrentTCB->pcTaskName, 0, 0);
_Unwind_Backtrace(unwind_backtrace_callback, 0);
while(1) Wdog_Service();
}
\u展开\u原因\u代码展开\u回溯\u回调(\u展开\u上下文*上下文,void*arg)
{
静态uint8_t行=1;
字符str_buff[buff_SIZE];
uintpttr\u t pc=\u Unwind\u GetIP(上下文);
如果(pc和行<最大行){
snprintf(str_buff,sizeof(str_buff),%d..0x%x”,行,pc);
打印字符串(str_buff,0,行大小*行++);
}
返回(来源)无原因;;
}
void WDOG1_驱动器QHandler(void)
{
打印字符串(pxCurrentTCB->pcTaskName,0,0);
_展开回溯(展开回溯回调,0);
而(1)Wdog_服务();
}
事实证明,OpenMRN完全实现了您所寻找的解决方案:
更多信息可在此处找到:。引用这篇文章:
这是可行的,但需要访问libgcc如何实现_Unwind_Backtrace函数的内部细节。幸运的是,代码是开源的,但依赖于这些内部细节是脆弱的,因为它可能会在未来的armgcc版本中中断,而不需要任何通知
通常,通过执行回溯的libgcc源读取,它会创建CPU核心寄存器的内存虚拟表示,然后使用此表示向上遍历堆栈,模拟异常抛出。_Unwind_Backtrace所做的第一件事是从当前CPU寄存器填充此上下文,然后调用内部实现函数
在大多数情况下,从堆栈异常结构手动创建该上下文足以通过调用堆栈伪造从处理程序模式向上的回溯。下面是一些示例代码(来自):
此代码旨在使用计时器中断获取CPU配置文件,但回溯展开可以从任何处理程序(包括故障处理程序)重用。从头到尾阅读代码:
- 重要的是,IRQ函数必须使用属性
定义,否则GCC的函数条目头将以不可预知的方式操纵CPU的状态,例如修改堆栈指针\uuuuuuuuuuuuuuuuuuuuuuuuuuuu
- 首先,我们保存所有不在异常条目结构中的其他核心寄存器。我们需要在一开始就从程序集开始这样做,因为当它们被用作临时寄存器时,通常会被以后的C代码修改
- 然后从中断前重构堆栈指针;无论处理器以前是在处理程序模式还是线程模式下,代码都会工作。此指针是异常条目结构。这段代码不处理非4字节对齐的堆栈,但我从未见过armgcc这样做
- 代码的其余部分是C/C++,我们填充从libgcc获取的内部结构,然后调用展开过程的内部实现。我们需要对libgcc的某些假设进行一些调整,这些假设在异常输入时不成立
- 有一种特定情况下,展开不起作用,即如果异常发生在一个叶函数中,该叶函数在进入时不将LR保存到堆栈中。当您尝试从进程模式执行回溯时,这种情况永远不会发生,因为被调用的回溯函数将确保调用函数不是叶。我试图通过在回溯过程中调整LR寄存器来应用一些变通方法,但我不相信它每次都有效。我对如何做得更好的建议很感兴趣
信息+对问题的更好解释。这一问题仍未解决。你应该能够编辑你的问题自己,用你在评论中添加的链接来精炼信息。考虑添加更多的细节到这个帖子而不是URL。最终,所有URL都将更改或脱机,此时此答案将不会为未来的访问者提供太多有用的信息。欢迎提供指向解决方案的链接,但请确保您的答案在没有该链接的情况下是有用的:在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在,然后引用你链接到的页面最相关的部分,以防目标页面不可用。只不过是一个链接的答案可能会被删除。
/// This struct definition mimics the internal structures of libgcc in
/// arm-none-eabi binary. It's not portable and might break in the future.
struct core_regs
{
unsigned r[16];
};
/// This struct definition mimics the internal structures of libgcc in
/// arm-none-eabi binary. It's not portable and might break in the future.
typedef struct
{
unsigned demand_save_flags;
struct core_regs core;
} phase2_vrs;
/// We store what we know about the external context at interrupt entry in this
/// structure.
phase2_vrs main_context;
/// Saved value of the lr register at the exception entry.
unsigned saved_lr;
/// Takes registers from the core state and the saved exception context and
/// fills in the structure necessary for the LIBGCC unwinder.
void fill_phase2_vrs(volatile unsigned *fault_args)
{
main_context.demand_save_flags = 0;
main_context.core.r[0] = fault_args[0];
main_context.core.r[1] = fault_args[1];
main_context.core.r[2] = fault_args[2];
main_context.core.r[3] = fault_args[3];
main_context.core.r[12] = fault_args[4];
// We add +2 here because first thing libgcc does with the lr value is
// subtract two, presuming that lr points to after a branch
// instruction. However, exception entry's saved PC can point to the first
// instruction of a function and we don't want to have the backtrace end up
// showing the previous function.
main_context.core.r[14] = fault_args[6] + 2;
main_context.core.r[15] = fault_args[6];
saved_lr = fault_args[5];
main_context.core.r[13] = (unsigned)(fault_args + 8); // stack pointer
}
extern "C"
{
_Unwind_Reason_Code __gnu_Unwind_Backtrace(
_Unwind_Trace_Fn trace, void *trace_argument, phase2_vrs *entry_vrs);
}
/// Static variable for trace_func.
void *last_ip;
/// Callback from the unwind backtrace function.
_Unwind_Reason_Code trace_func(struct _Unwind_Context *context, void *arg)
{
void *ip;
ip = (void *)_Unwind_GetIP(context);
if (strace_len == 0)
{
// stacktrace[strace_len++] = ip;
// By taking the beginning of the function for the immediate interrupt
// we will attempt to coalesce more traces.
// ip = (void *)_Unwind_GetRegionStart(context);
}
else if (last_ip == ip)
{
if (strace_len == 1 && saved_lr != _Unwind_GetGR(context, 14))
{
_Unwind_SetGR(context, 14, saved_lr);
allocator.singleLenHack++;
return _URC_NO_REASON;
}
return _URC_END_OF_STACK;
}
if (strace_len >= MAX_STRACE - 1)
{
++allocator.limitReached;
return _URC_END_OF_STACK;
}
// stacktrace[strace_len++] = ip;
last_ip = ip;
ip = (void *)_Unwind_GetRegionStart(context);
stacktrace[strace_len++] = ip;
return _URC_NO_REASON;
}
/// Called from the interrupt handler to take a CPU trace for the current
/// exception.
void take_cpu_trace()
{
memset(stacktrace, 0, sizeof(stacktrace));
strace_len = 0;
last_ip = nullptr;
phase2_vrs first_context = main_context;
__gnu_Unwind_Backtrace(&trace_func, 0, &first_context);
// This is a workaround for the case when the function in which we had the
// exception trigger does not have a stack saved LR. In this case the
// backtrace will fail after the first step. We manually append the second
// step to have at least some idea of what's going on.
if (strace_len == 1)
{
main_context.core.r[14] = saved_lr;
main_context.core.r[15] = saved_lr;
__gnu_Unwind_Backtrace(&trace_func, 0, &main_context);
}
unsigned h = hash_trace(strace_len, (unsigned *)stacktrace);
struct trace *t = find_current_trace(h);
if (!t)
{
t = add_new_trace(h);
}
if (t)
{
t->total_size += 1;
}
}
/// Change this value to runtime disable and enable the CPU profile gathering
/// code.
bool enable_profiling = 0;
/// Helper function to declare the CPU usage tick interrupt.
/// @param irq_handler_name is the name of the interrupt to declare, for example
/// timer4a_interrupt_handler.
/// @param CLEAR_IRQ_FLAG is a c++ statement or statements in { ... } that will
/// be executed before returning from the interrupt to clear the timer IRQ flag.
#define DEFINE_CPU_PROFILE_INTERRUPT_HANDLER(irq_handler_name, CLEAR_IRQ_FLAG) \
extern "C" \
{ \
void __attribute__((__noinline__)) load_monitor_interrupt_handler( \
volatile unsigned *exception_args, unsigned exception_return_code) \
{ \
if (enable_profiling) \
{ \
fill_phase2_vrs(exception_args); \
take_cpu_trace(); \
} \
cpuload_tick(exception_return_code & 4 ? 0 : 255); \
CLEAR_IRQ_FLAG; \
} \
void __attribute__((__naked__)) irq_handler_name(void) \
{ \
__asm volatile("mov r0, %0 \n" \
"str r4, [r0, 4*4] \n" \
"str r5, [r0, 5*4] \n" \
"str r6, [r0, 6*4] \n" \
"str r7, [r0, 7*4] \n" \
"str r8, [r0, 8*4] \n" \
"str r9, [r0, 9*4] \n" \
"str r10, [r0, 10*4] \n" \
"str r11, [r0, 11*4] \n" \
"str r12, [r0, 12*4] \n" \
"str r13, [r0, 13*4] \n" \
"str r14, [r0, 14*4] \n" \
: \
: "r"(main_context.core.r) \
: "r0"); \
__asm volatile(" tst lr, #4 \n" \
" ite eq \n" \
" mrseq r0, msp \n" \
" mrsne r0, psp \n" \
" mov r1, lr \n" \
" ldr r2, =load_monitor_interrupt_handler \n" \
" bx r2 \n" \
: \
: \
: "r0", "r1", "r2"); \
} \
}