1
votes

Freertos uses the code section below for fault handlers, to get information out of the current stack pointer (the task-stack which caused the crash). unfortunately this works only based on exceptions, and if the the causing function is the last call in the stack.

    "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"

I am looking for the same idea of implementation as above, but with the ability to give me the stack info if the function causing the error was not the last caller, as in the example below

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);
}

So here, it would show me that the function "my_assert()" is causing the issue, but this is not true, its the caller of "my_assert()".

Questions:

1

1 Answers

0
votes

There are several problems with this idea, unfortunately:

  • The 'cause' of any kind of fault is difficult to determine and depends what you mean. You claim that the my_assert(0) call is the cause of the fault in your example, but that's not really true; the cause is that the my_assert function itself raises a fault. On the basis that a fault is not the intended behaviour (no sane implementation of assert would deliberately trigger a fault, surely) it's the my_assert function that needs debugging first. In general, how is the fault handler supposed to know how far to unroll the stack?

  • Stack unrolling is impossible anyway without significant extra knowledge. How do you know what the stack frame of the function that raised the fault (my_assert in your example) looks like? It could have stored hundreds of bytes of local variables on the stack prior to the fault being triggered. How do you know what you're looking at? Debuggers find this out by using debug information added to the compiled binary (e.g. using gcc -g).

  • Even finding out where the fault was triggered is not always trivial. Take the typical case of branching to an invalid address, perhaps due to a buffer overrun corrupting a return address on the stack. The CPU will usually perform the branch, and then on attempting an instruction fetch will find that the address in question is not marked as executable and will fault. On entry to handler mode the PC is stacked, but the stacked value is that of the invalid memory location, not of the code that attempted the branch to that location. If you're very lucky the instruction that caused the branch was a BL or BLX (a call, effectively) and you'll find the intended return address in the stacked link register value. But in my experience this is not common, and the best you'll get from the stacked LR is a vague clue about the last call that was made successfully prior to the fault, which could have been some time previously.

All in all, it's a nightmare. This is what debuggers are for, honestly, and even then debugging faults can be very tricky.

In answer to your two specific questions:

  1. Almost, maybe. You mean 0x1C - there's no need to add an extra 4 bytes to that - and this will find the previous stack top only if you don't have a hardware FPU in use. It should work on the Cortex-M3 but would be unreliable on the -M4F, for example.

  2. Nope. You're working with CPU implementation details here; you need controlled access to individual registers and there's no way C can give you that even using the CMSIS extensions.