Stm32 hardfault and search method of memory overflow

STM32 memory structure

1. Key points
1.1 two storage types: RAM and Flash
Ram is readable and writable. On the memory structure of STM32, RAM address segment distribution [0x2000_0000, 0x2000_0000 + RAM size)
Flash is read-only. On the memory structure of STM32, the flash address field [0x0800_0000, 0x2000_0000)
1.2 six types of stored data segments: data/.bss/.text/.constdata/heap/stack
. data data segment: used to store global and static variables initialized but not initialized to 0. It is readable and writable
. bss(Block Started by Symbol) data segment: used to store global variables and static variables that are not initialized or initialized to 0. It is readable and writable. If not initialized, the system will initialize the variables to 0
. text code segment: used to put the program code. After the code is compiled, it will be stored here for a long time
. constdata read-only constant data segment: const limited data types are stored here, read-only
Heap heap: we usually only talk about dynamic memory allocation, which is managed by memory allocator and applied and released by malloc/free
Stack stack area: used to save local variables and parameters of functions during code execution. Its operation mode is similar to the stack in the data structure. It is a "Last In First Out (LIFO) data structure. This means that the last data put on the stack will be the first data removed from the stack. LIFO is an ideal data structure for temporarily stored information and information that does not need to be saved for a long time. After calling a function or procedure, the system usually clears the local variables, function call information and other information stored on the stack. The top of the stack is usually at the end of the read-write RAM area, and its address space is usually "reduced downward", that is, the more data stored on the stack, the smaller the address of the stack.

1.3 three storage attribute areas: RO/RW/ZI
RO (Read Only): a read-only area that needs to be saved for a long time. It is burned to the Rom/Flash section and the data section above text segments and constdata segment belongs to this attribute area (sometimes. constdata segment is also called ro data segment, which should be distinguished from this generalized RO)
RW (Read Write): read and write initialized global variable and static variable segments, as described above data section belongs to RW area
ZI (Zero Init): it is not initialized or initialized to 0. When the system is powered on, it will actively initialize the data in this area, as described above The bss section is In addition, you can look at the map file compiled by Keil tool. The Heap and Stack areas are also marked with Zero attributes. Therefore, Heap and Stack can also be considered as ZI areas
RW area is special, readable and writable, but initialized, because the data in RAM can not be saved after power failure, so the RW area is empty Data segment data also needs to be saved in Rom/Flash, and such data will be copied to RAM area for reading and writing when powered on. The data in the ZI area does not need to be saved after power failure. It can be used when it is initialized to 0 when directly powered on, so it does not need to be saved in ROM. Thus, the formula for calculating RAM/ROM occupied space is:

ROM Size = .text + .constdata + .data (RO + RW)
RAM Size = .bss + .data (ZI + RW)

Here, Stack and Heap areas are not considered in RAM size calculation, and the actual size is greater than this, because these two areas have dynamic complexity and are difficult to estimate.

Example of defining a global array variable:

1. static unsigned char test[1024]; / / global, uninitialized, ZI area, does not affect ROM size
2. static unsigned char test[1024] = {0}; / / global, initialized to 0, ZI area, does not affect ROM size
3. static unsigned char test[1024] = {1}; / / global, initialized to non-0, RW(.data) area, ROM Size expanded
1.4 expand Heap
The startup code in STM32 is startup * S file, the heap size is generally defined as follows:

Heap_Size      EQU     0x200;

In practical use, this area may be more complex than the concise description mentioned in section 1.2.

Many small projects do not use the memory allocator: due to various reasons (insufficient RAM, simple program, etc.), some necessary large or fixed memory is defined and used directly in the form of array, bypassing the memory allocator. So this time, heap_ The existence of size is meaningless, heap_ The larger the size definition, the more space is wasted. You can directly Heap_Size is defined as 0. At this time, the space provided by the heap area may be defined in bss segment (global / static array not initialized), or Data (global / static data is initialized to non-0), or on the stack (local array variables are used, Tips: but large arrays are not recommended to be defined in the stack, otherwise the stack may overflow)
Re implement the memory allocator: instead of directly mapping the memory allocator to the heap, first define a large array memory (possibly in. bss or. data. To avoid storage in ROM, it is better to use. bss), and then give this memory to the memory allocator
The memory allocator directly uses the Heap area: at this time, it is necessary to calculate how much space is reserved for the Stack area. If more space is reserved, the Stack will not be wasted; Leaving too little is likely to cause Stack overflow and program crash
In addition to using its own ram, it also uses external extended ram: This requires a memory allocator to manage several pieces of RAM space with discontinuous addresses

The keil compilation connection of Stm32 is shown in the figure above.

The compilation information includes the following parts:
1) Code: Code segment, which stores the Code part of the program
2) RO data: read only data segment, which stores constants defined in the program;
3) RW data: read and write data segments to store global variables initialized to non-0 values
4) Zi data: zero data segment, which stores uninitialized global variables and variables initialized to 0;

After compiling the project, a will be generated map file, which describes the size and address occupied by each function. The last lines of the file also explain the relationship between the above fields:

Total RO  Size (Code + RO Data)                46052 (  44.97kB)
Total RW  Size (RW Data + ZI Data)             36552 (  35.70kB)
Total ROM Size (Code + RO Data + RW Data)      46212 (  45.13kB)
1) RO Size includes Code and RO data, indicating the size of Flash space occupied by the program
2) RW Size includes RW data and Zi data, indicating the size of RAM occupied during operation
3) ROM Size includes Code, RO Data and RW Data, indicating the size of Flash space occupied by burning program

Memory occupied by program in STM32
Code, RO data, RW data and Zi data under Keil MDK:

Code stores the program code.
Ro data stores const constants and instructions.
RW data stores global variables whose initialization value is not 0.
Zi data stores uninitialized global variables or global variables with an initialization value of 0.
Occupied Flash=Code + RO Data + RW Data;

Maximum ram consumed in operation = RW data + Zi data;

This is the size of each segment that can be obtained after MDK compilation, such as Code R0 RW ZI in the Program Size below

It can be calculated that the occupied FLASH = 34456+456+172=34.26kB, and the occupied RAM=172+18908=18.63kB

How is the stack allocated? The memory occupation of the stack is allocated at the address after RAM is allocated to RW data + Zi data.

Heap: the memory area where the compiler calls dynamic memory allocation.

Stack: when the program is running, the local variables are first in and last out. This structure is suitable for program call, so if the array of local variables is too large, it may cause stack overflow

Stack overflow can easily lead to HaltFault.

The stack size is set in the startup file start_stmf103xb.s (take STM32F103 as an example):

Solution to global variable being changed for unknown reason

There are always some strange problems in the development process. During the simulation, it is found that a global variable has been changed inexplicably, resulting in problems in the judgment of the whole function.
Global variables may be changed for the following reasons:

1. Self modified (nonsense ~): check who called this variable

2. Global variable bytes are not aligned:
Once during debugging, it was found that a variable defined as a local variable can run normally, while a variable defined as a global variable cannot run. Local variables can run, which shows that the logic of my program is no problem. When looking for the reason, it seems that my global variables are often changed inexplicably. After looking around, I found that this variable was not used by other functions at all.
Later, the address of the variable (assumed to be 0x1002) is obtained through simulation. After dividing the address by 4, it is found that it is not an integer, which is found that the variable bytes are not aligned with 4 bytes. As for why not align, I don't know!
Solution: use the attribute((aligned(4))) modifier to align the 4 bytes, which is a perfect solution.

3. Pointer not initialized:
If the variable you define is a pointer type, failure to initialize it will cause the pointer to be a wild pointer, and the value in it is uncertain.

In short, in the process of development, use less global variables. If you want to use them, use structures as much as possible, do a good job in layering, and improve the readability and portability of the code.

Cause analysis of STM32 hardfault

In fact, the wild pointer, array out of bounds, stack overflow, etc. all trigger the hardfault because one or more of the bus exception, memory management exception and usage exception are triggered.

1. Memory overflow, including stack overflow.
The relationship between MCU memory and stack can be referred to
http://blog.csdn.net/c12345423/article/details/53004747
2. Cross border visits.
This is often the use of exponential groups. Specifically, when accessing the sixth element of an array with only five elements, the
Cross border visits have now been made. This error often occurs when an array is passed in as a function parameter, because only
Pointer, and if the translation amount of pointer access is not determined in the function, there may be an out of bounds access error. Worth noting
It means that the C language does not compile queries with cross-border access, that is, it does not detect whether they are valid at compile time
There are cross-border visits.
3. Abnormal error caused by incorrect use of flash.
First, when using flash to store data, its storage space may overlap with the code area; Second, because
According to your own requirements, you need to convert the pointing type of the pointer to flash, such as float *, so that the pointer can
Flash moves at intervals of 4 units, but since flash is partitioned, if the area header address and are transferred
If the interval between pointers is not a multiple of 4, an error will also occur.
4. In this year, everyone drew their own PCB and wrote their own programs. Sometimes they found that PCB welding would also cause problems
HardFault, and this error will exist from the beginning of the program.
5. Common C syntax errors such as wild pointer addressing, dividing by 0 (inf may also be obtained without errors).

 

Kinetis MCU adopts Cortex-M4 kernel, whose fault exception can capture illegal memory access and illegal programming behavior. Fault exception can detect the following illegal behaviors:
Bus Fault: a memory access error is detected during address fetching, data read / write, interrupt variable fetching, register stack operation (stack in / out) during interrupt entry / exit.
Memory management Fault: it is detected that the memory access violates the area defined by the memory protection unit (MPU).
Usage Fault: an undefined instruction exception was detected and its multiple load / store memory access was not. If the corresponding control bits are enabled, divisor zero and other misaligned memory accesses can also be detected.
Hard Fault: if the above processing programs of bus Fault, memory management Fault and usage Fault cannot be executed (for example, exceptions of bus Fault, memory management Fault and usage Fault are disabled or new faults appear in these exception handlers), the hard Fault will be triggered.

In order to explain the principle of the Fault interrupt handler, the processing process of MCU when the system generates an exception is restated here:
  1. There is a stack pressing process. If PSP (process stack pointer) is used when an exception occurs, it will be pressed into PSP. If MSP (main stack pointer) is used when an exception occurs, it will be pressed into MSP.
  2. The LR value will be set according to the processor mode and the stack used (of course, the set LR value will be pressed on the stack).
  3. When the exception is saved, the hardware automatically pushes the values of 8 registers into the stack (the 8 registers are xPSR, PC, LR, R12 and R3~R0 in turn). If the current code is using PSP when the exception occurs, press the above 8 registers into PSP; Otherwise press in MSP.

When the system generates an exception, we need two key register values, one is PC and the other is LR (link register). Find the corresponding stack through LR, and then find the PC value triggering the exception through the stack. Take out the PC value pushed into the stack when an exception occurs, and compare it with the disassembled code to get which instruction has an exception. Here is an explanation of how LR registers work. As mentioned above, when the Cortex-M4 processor accepts an exception, some register values in the register group will be automatically pushed into the current stack space, including the link register (LR). At this time, the LR will be updated to the special value (EXC_RETURN) required for exception return.
About exc_ The definition of return is as follows. It is a 32-bit value. The high 28 position is 1. Bits 0 to 3 provide the information required by the exception return mechanism, as shown in the table below. It can be seen that the second bit indicates whether the stack used before entering the exception is MSP or PSP. At the end of exception handling, MCU needs to allocate SP value according to this value. This is also the principle used to judge the stack used in this method. Its implementation method can be seen from the following_ init_ hardfault_ See in ISR.

In addition, we can use the console serial port of MQX to output Fault exception information to help debugging. When writing a Fault handler, the default Fault handler in the startup code will be replaced with the Fault handler you need. It should be noted that the console serial port of MQX can only use the POLL polling mode driver, not the interrupt mode driver, because the printout is performed in the interrupt mode.
Users can write custom hard Fault handlers_ int_hardfault_isr, modify the interrupt vector definition of MQX c. Put the default inside_ Replace the vector code segment with the following code. When a hard Fault exception occurs in the system, custom Fault processing will be called_ int_hardfault_isr function. In this function, we can backtrack the problem code through stacktrace back.

We can_ int_ hardfault_ The ISR function prints out the register, stack, status register and other information when an exception occurs. If the system is abnormal, the values of LR and PC will be printed through the serial port console. Then, according to the map file generated by the compiler, find the specific function with the problem.

 

From the serial port output in the figure above, we can see the values of PC and LR registers. The value of PC is 0x56c6. We can find the instructions with problems according to the assembly code. This greatly reduces the scope of finding the problem, and can help developers quickly locate the root cause of the problem.

Appendix Fault exception interrupt handling code:

// hard fault handler in C,
// with stack frame location as input parameter
void hard_fault_handler_c (unsigned int * hardfault_args)
{
  unsigned int stacked_r0;
  unsigned int stacked_r1;
  unsigned int stacked_r2;
  unsigned int stacked_r3;
  unsigned int stacked_r12;
  unsigned int stacked_lr;
  unsigned int stacked_pc;
  unsigned int stacked_psr;
 
  stacked_r0 = ((unsigned long)hardfault_args[0]);
  stacked_r1 = ((unsigned long)hardfault_args[1]);
  stacked_r2 = ((unsigned long)hardfault_args[2]);
  stacked_r3 = ((unsigned long)hardfault_args[3]);
 
  stacked_r12 = ((unsigned long)hardfault_args[4]);
  stacked_lr = ((unsigned long)hardfault_args[5]);
  stacked_pc = ((unsigned long)hardfault_args[6]);
  stacked_psr = ((unsigned long) hardfault_args[7]);
 
  printf ("\n\n[Hard faulthandler - all numbers in hex]\n");
  printf ("R0 = %x\n",stacked_r0);
  printf ("R1 = %x\n",stacked_r1);
  printf ("R2 = %x\n",stacked_r2);
  printf ("R3 = %x\n",stacked_r3);
  printf ("R12 = %x\n",stacked_r12);
  printf ("LR [R14] = %x  subroutine call return address\n",stacked_lr);
  printf ("PC [R15] = %x  program counter\n", stacked_pc);
  printf ("PSR = %x\n",stacked_psr);
 
  /******************* Add yourdebug trace here ***********************/
  _int_kernel_isr();
}
 
/* hard fault interrupt handler */
void _int_hardfault_isr( )
{
  __asm("TST LR, #4");
  __asm("ITE EQ");
  __asm("MRSEQ R0,MSP");
  __asm("MRSNE R0,PSP");
  __asm("Bhard_fault_handler_c");
}

 

  1. When there is no JTAG, print the stack information through the serial port:

     /* Private typedef -----------------------------------------------------------*/
      enum { r0, r1, r2, r3, r12, lr, pc, psr};
      
      /* Private define ------------------------------------------------------------*/
      /* Private macro -------------------------------------------------------------*/
      /* Private variables ---------------------------------------------------------*/
      extern __IO uint16_t ADC_InjectedConvertedValueTab[32];
      uint32_t Index = 0;
      
      /* Private function prototypes -----------------------------------------------*/
      void Hard_Fault_Handler(uint32_t stack[]);
      
      /* Private functions ---------------------------------------------------------*/
      
      static void printUsageErrorMsg(uint32_t CFSRValue)
      {
          printf("Usage fault: \r\n");
          CFSRValue >>= 16; // right shift to lsb
      
          if((CFSRValue & (1<<9)) != 0) {
              printf("Divide by zero \r\n");
          }
          if((CFSRValue & (1<<8)) != 0) {
              printf("Unaligned access \r\n");
          }
      }
      
      static void printBusFaultErrorMsg(uint32_t CFSRValue)
      {
          printf("Bus fault: \r\n");
          CFSRValue = ((CFSRValue & 0x0000FF00) >> 8); // mask and right shift to lsb
      }
      
      static void printMemoryManagementErrorMsg(uint32_t CFSRValue)
      {
          printf("Memory Management fault: \r\n");
          CFSRValue &= 0x000000FF; // mask just mem faults
      }
      
      static void stackDump(uint32_t stack[])
      {
        static char msg[80];
        sprintf(msg, "R0 = 0x%08x\r\n", stack[r0]); printf(msg);
        sprintf(msg, "R1 = 0x%08x\r\n", stack[r1]); printf(msg);
        sprintf(msg, "R2 = 0x%08x\r\n", stack[r2]); printf(msg);
        sprintf(msg, "R3 = 0x%08x\r\n", stack[r3]); printf(msg);
        sprintf(msg, "R12 = 0x%08x\r\n", stack[r12]); printf(msg);
        sprintf(msg, "LR = 0x%08x\r\n", stack[lr]); printf(msg);
        sprintf(msg, "PC = 0x%08x\r\n", stack[pc]); printf(msg);
        sprintf(msg, "PSR = 0x%08x\r\n", stack[psr]); printf(msg);
      }
      
      void Hard_Fault_Handler(uint32_t stack[])
      {
        static char msg[80];
        //if((CoreDebug->DHCSR & 0x01) != 0) {
          printf("\r\nIn Hard Fault Handler\r\n");
          sprintf(msg, "SCB->HFSR = 0x%08x\r\n", SCB->HFSR);
          printf(msg);
          if ((SCB->HFSR & (1 << 30)) != 0) {
            printf("Forced Hard Fault\r\n");
            sprintf(msg, "SCB->CFSR = 0x%08x\r\n", SCB->CFSR );
            printf(msg);
            if((SCB->CFSR & 0xFFFF0000) != 0) {
              printUsageErrorMsg(SCB->CFSR);
            }
            if((SCB->CFSR & 0xFF00) != 0) {
              printBusFaultErrorMsg(SCB->CFSR);
            }
            if((SCB->CFSR & 0xFF) != 0) {
              printMemoryManagementErrorMsg(SCB->CFSR);
            }
          }
          stackDump(stack);
          __ASM volatile("BKPT #01");
        //}
        while(1);
      }
      
      
      __ASM void HardFault_Handler_a(void)
      {
        IMPORT Hard_Fault_Handler
      
        TST lr, #4
        ITE EQ
        MRSEQ r0, MSP
        MRSNE r0, PSP
        B Hard_Fault_Handler
      }
      
      /**
      * @brief This function handles Hard Fault exception.
      * @param None
      * @retval None
      */
      void HardFault_Handler(void)
      {
        /* Go to infinite loop when Hard Fault exception occurs */
       HardFault_Handler_a();
      }

 

Keywords: C stm32 ARM

Added by weezil on Thu, 06 Jan 2022 03:45:13 +0200