【问题标题】:FreeRTOS - Restore Stackpointer for last function callFreeRTOS - 为最后一次函数调用恢复堆栈指针
【发布时间】:2020-05-11 05:41:50
【问题描述】:

Freertos 使用下面的代码部分作为故障处理程序,从当前堆栈指针(导致崩溃的任务堆栈)中获取信息。不幸的是,这只适用于异常,并且如果导致函数是堆栈中的最后一个调用。

    "tst lr, #4                                                     \n"
    "ite eq                                                         \n"
    "mrseq r0, msp                                                  \n"
    "mrsne r0, psp                                                  \n"
    "ldr r1, ADDRESS                         \n"
    "bx r1                                                          \n"
    "ADDRESS: .word stackdump_printf   \n"

我正在寻找与上述相同的实现理念,但如果导致错误的函数不是最后一个调用者,则能够向我提供堆栈信息,如下例所示

lumos_c_mangling void getRegistersFromStack( uint32_t *pulFaultStackAddress )
{
  uint32_t dummy;
  /* These are volatile to try and prevent the compiler/linker optimising them
     away as the variables never actually get used.  If the debugger won't show the
     values of the variables, make them global my moving their declaration outside
     of this function. */
  volatile uint32_t r0;
  volatile uint32_t r1;
  volatile uint32_t r2;
  volatile uint32_t r3;
  volatile uint32_t r12;
  volatile uint32_t lr; /* Link register. */
  volatile uint32_t pc; /* Program counter. */
  volatile uint32_t psr;/* Program status register. */

  r0 = pulFaultStackAddress[ 0 ];
  r1 = pulFaultStackAddress[ 1 ];
  r2 = pulFaultStackAddress[ 2 ];
  r3 = pulFaultStackAddress[ 3 ];

  r12 = pulFaultStackAddress[ 4 ];
  lr  = pulFaultStackAddress[ 5 ];
  pc  = pulFaultStackAddress[ 6 ];
  psr = pulFaultStackAddress[ 7 ];

  /* When the following line is hit, the variables contain the register values. */
  for(;;);
}

void Hardfault_Handler()
{
  __asm__
  (
      " tst lr, #4                                                \n"
      " ite eq                                                    \n"
      " mrseq r0, msp                                             \n"
      " mrsne r0, psp                                             \n"
      " ldr r1, [r0, #24]                                         \n"
      " ldr r2, SP_ADDRESS_CONST                                  \n"
      " bx r2                                                     \n"
      " SP_ADDRESS_CONST: .word getRegistersFromStack             \n"
  );
}

void my_assert(int exp) {
  if(!exp) {
    // here set exception (for debug purposes, i will set here hardfault active (ignore this please in the current discussion)
    NVIC_SetPendingIRQ(HardFault_IRQn);
  }
}

void main() {
  my_assert(0);
}

所以在这里,它会告诉我函数“my_assert()”导致了问题,但这不是真的,它是“my_assert()”的调用者。

问题:

【问题讨论】:

    标签: freertos cortex-m3


    【解决方案1】:

    不幸的是,这个想法有几个问题:

    • 任何故障的“原因”都很难确定,取决于您的意思。您声称 my_assert(0) 调用是您示例中错误的原因,但这不是真的;原因是my_assert 函数本身引发了错误。基于错误不是预期的行为(assert 的合理实现肯定会故意触发错误),首先需要调试的是 my_assert 函数。一般来说,故障处理程序应该如何知道将堆栈展开多远?

    • 如果没有大量的额外知识,无论如何都无法展开堆栈。你怎么知道引发错误的函数的堆栈框架(在你的例子中是my_assert)是什么样的?在触发故障之前,它可能已经在堆栈上存储了数百字节的局部变量。你怎么知道你在看什么?调试器通过使用添加到已编译二进制文件中的调试信息来发现这一点(例如,使用gcc -g)。

    • 即使找出触发故障的位置也并非易事。以跳转到无效地址的典型情况为例,这可能是由于缓冲区溢出破坏了堆栈上的返回地址。 CPU 通常会执行分支,然后在尝试取指令时会发现有问题的地址没有被标记为可执行并且会出错。在进入处理程序模式时,PC 被堆叠,但堆叠的值是无效内存位置的值,而不是尝试分支到该位置的代码的值。如果您非常幸运,导致分支的指令是 BLBLX(实际上是一个调用),您将在堆栈链接寄存器值中找到预期的返回地址。但根据我的经验,这并不常见,从堆积的LR 中得到的最好结果是关于在故障之前成功进行的最后一次调用的模糊线索,这可能是之前的一段时间。

    总而言之,这是一场噩梦。老实说,这就是调试器的用途,即使这样调试错误也可能非常棘手。

    回答您的两个具体问题:

    1. 几乎,也许。您的意思是 0x1C - 无需添加额外的 4 个字节 - 只有当您没有使用硬件 FPU 时,它才会找到前一个堆栈顶部。例如,它应该在 Cortex-M3 上工作,但在 -M4F 上不可靠。

    2. 不。您在这里处理 CPU 实现细节;您需要对单个寄存器进行受控访问,即使使用 CMSIS 扩展,C 也无法为您提供。

    【讨论】:

    • 我使用 0x1C + 4 作为位置 0x1C 本身仍然是一个 32 位宽的变量,因此我假设 0x1C+4==长度。这里的想法比你想象的要简单:my_assert 有一个已知的堆栈使用,因此,如果我让断言函数设置异常(不是硬故障,这只是一个例子),我可以将 SP 指针校正补偿回调用者(主),因为我知道顶部堆栈校正值(这是函数 my_assert,删除它,我知道调用 SP。
    • 0x1C 是在异常进入期间推送的项目数(没有 FPU),因此向堆栈指针添加 0x1C 会将其恢复到其异常前位置。
    • 如果您的用例如此具体,那么恐怕我必须重复我的断言“assert 的合理实现不会故意触发错误”。为什么不在my_assert 中使用软件断点或其他东西?为什么允许它触发故障?
    • 作为堆栈转储,在现场被芯片锁定的设备中,并通过 uart 获得崩溃报告。
    • 你不需要错误就可以做到这一点。如果您出于某种原因绝对必须处于处理程序模式(无法想象为什么需要它,但无论如何),您可以使用 SVC 处理程序 - 实际上您使用宏来定义您的断言,以便 my_assert() 函数不' 甚至不存在,并且 SVC 处理程序将直接从断言失败的函数调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    • 2017-06-06
    • 2012-06-22
    • 1970-01-01
    • 2014-07-10
    • 2013-01-20
    • 2020-01-17
    相关资源
    最近更新 更多