cancel
Showing results for 
Search instead for 
Did you mean: 

Hard faults due to (gcc) compiler computing incorrect addresses

lamare
Associate II

I've encountered a number of compiler problems in a project to port freemodbus on an STM32G4 nucleo board, which is available on github. There's a demo application specifically for the NUCLEO-G431RB:

https://github.com/alammertink/FreeModbusDemo

This uses the library ported to STM32 using the cmake toolchain:

https://github.com/alammertink/freemodbus 

https://github.com/alammertink/freemodbus/blob/master/demo/STM32_CMAKE/README.md 

 

I've tested STM32CubeCLT versions 1.16 to 1.18 on Windows, which all produce the same problem: Hard fault interrupts occurring because of some error in the address calculation of function pointers and even byte arrays, which does not have anything to do with alignment issues.

I've worked around these errors using inline assembly, where the workarounds are conditionally compiled using the 

STM32_CMAKE macro. The problems occur in mb.c where a/o a number of callback function pointers are set:
 
 
#ifdef STM32_CMAKE // work around nasty gcc compiler bug { uint32_t* srcPtr = NULL; uint32_t* destPtr = NULL; __asm__ volatile ("ldr %0, =eMBRTUStart" : "=r" (pvMBFrameStartCur)); __asm__ volatile ("ldr %0, =eMBRTUStop" : "=r" (pvMBFrameStopCur)); __asm__ volatile ("ldr %0, =eMBRTUSend" : "=r" (peMBFrameSendCur)); __asm__ volatile ("ldr %0, =eMBRTUReceive" : "=r" (peMBFrameReceiveCur)); pvMBFrameCloseCur = NULL; // Assign pxMBFrameCBByteReceived __asm__ volatile ("ldr %0, =xMBRTUReceiveFSM" : "=r" (srcPtr)); __asm__ volatile ("ldr %0, =pxMBFrameCBByteReceived" : "=r" (destPtr)); *destPtr = (uint32_t)srcPtr; // Assign pxMBFrameCBTransmitterEmpty __asm__ volatile ("ldr %0, =xMBRTUTransmitFSM" : "=r" (srcPtr)); __asm__ volatile ("ldr %0, =pxMBFrameCBTransmitterEmpty" : "=r" (destPtr)); *destPtr = (uint32_t)srcPtr; // Assign pxMBPortCBTimerExpired __asm__ volatile ("ldr %0, =xMBRTUTimerT35Expired" : "=r" (srcPtr)); __asm__ volatile ("ldr %0, =pxMBPortCBTimerExpired" : "=r" (destPtr)); *destPtr = (uint32_t)srcPtr; } #else pvMBFrameStartCur = eMBRTUStart; pvMBFrameStopCur = eMBRTUStop; peMBFrameSendCur = eMBRTUSend; peMBFrameReceiveCur = eMBRTUReceive; pvMBFrameCloseCur = MB_PORT_HAS_CLOSE ? vMBPortClose : NULL; pxMBFrameCBByteReceived = xMBRTUReceiveFSM; pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM; pxMBPortCBTimerExpired = xMBRTUTimerT35Expired; #endif
View more
 

And in mbrtu.c , where even the address of a byte buffer array was not computed correctly:

https://github.com/alammertink/freemodbus/blob/master/modbus/rtu/mbrtu.c 

#ifdef STM32_CMAKE // work around nasty gcc compiler bug volatile UCHAR* destPtr; __asm__ volatile ("ldr %0, =ucRTUBuf" : "=r" (destPtr)); destPtr += usRcvBufferPos; *destPtr = ucByte; usRcvBufferPos++; #else ucRTUBuf[usRcvBufferPos++] = ucByte; #endif

 

There are more problems in these two files, all worked around using inline assembly and conditionally compiled using the STM32_CMAKE macro, set in:

https://github.com/alammertink/freemodbus/blob/master/CMakeLists.txt 

 

The problems can be reproduced with just a Nucleo-G431RB board by un-setting the STM32_CMAKE macro. The function pointers are already set in the initialization, which will be hit without any further action. The errors in mbrtu.c require at least some data to be sent over the (virtual) com port, which can be done using modpoll as described in the readme:

https://github.com/alammertink/freemodbus/blob/master/demo/STM32_CMAKE/README.md 

 

As far as I can tell, this is a gcc compiler bug, since I've seen no warning nor errors, while the code in question has been running without problems on various platforms for years. 

 

14 REPLIES 14
TDK
Super User

> Hard fault interrupts occurring because of some error in the address calculation of function pointers and even byte arrays, which does not have anything to do with alignment issues.

Perhaps spend some time detailing this issue so that it can be addressed. 

The GCC compiler is robust, it's unlikely to have glaring issues, especially in "x = y" statements.

A complete compileable program which shows the issue is best.

If you feel a post has answered your question, please click "Accept as Solution".
Ozone
Principal

As poster TDK wrote, gcc is relatively stable and robust.

> As far as I can tell, this is a gcc compiler bug, since I've seen no warning nor errors, while the code in question has been running without problems on various platforms for years. 

This statement lacks context.
Dealing with a (small) RTOS on several different platforms in my dayjob, it is usually not that simple.
Especially with different architectures, variable sizes ('int'), alignment requirements, and function pointers, maintaining common code quickly becomes messy.

My bet is on alignment issues.

@Ozone  If it were an alignment issue, it could not be worked around using inline assembly.

lamare
Associate II

@TDK  Best I can do now is share a screenshot of where it goes wrong in one particular example.

The complete project is open source and can be compiled. 

It seems pretty clear there is a problem with the generated assembly, which is that r4 is being used without having been initialized:

lamare_0-1745936168668.png

In the current highlighted line, r4 has a value of 0x20000104, while r3=0.

Stepping through, we get a value of 0xf0006008 in r3:

lamare_1-1745936321447.png

At this point, the next statement (strb r1,[r3,r2]) causes a hard fault writing to 0xf0006008.

 

Done some further analysis using ChatGPT using an earlier screenshot of the same c code been disassembled:

lamare_2-1745942918258.png

--::--

I re-checked your mbrtu.c file, and you are absolutely correct: on line 71, the actual definition is:

volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX];

with:

#define MB_SER_PDU_SIZE_MAX (256)

So ucRTUBuf is a statically allocated array, and not a pointer, meaning it should be accessed directly — not via an indirect pointer dereference like:

ldr r3, [r4, r3]  ; r3 = *(r4 + offset)

🧠 So what’s going wrong?

From your disassembly:

0x08006f60  ldr r3, [r4, r3]

This is not how a static array should be accessed. Normally, the compiler would emit something like:

ldr r3, =ucRTUBuf
...
strb r1, [r3, r2]

That is:

  • Load the base address of the array into r3

  • Write r1 into offset r2 within the array

But in your case, the compiler is using:

ldr r3, [r4, r3]  ; dereferencing a pointer from (r4 + offset)

And r4 = 0x20000104, leading to:

  • Bad pointer dereference → r3 = 0xF0006008

  • Then strb faults when writing to that address.


🧩 Hypothesis

You're most likely seeing corrupted code generation, incorrect symbol relocation, or an incorrect linker script.

Because ucRTUBuf is not a pointer, the compiler should never emit:

ldr r3, [r4, r3]

This only makes sense if ucRTUBuf were a pointer variable being accessed indirectly, e.g., via a structure field like:

struct {
    ...
    UCHAR *buf;
} someStruct;

someStruct.buf[usRcvBufferPos++] = ucByte;

But you're not doing that.

 

What the Assembly Should Look Like:

For the expression ucRTUBuf[usRcvBufferPos++] = ucByte;, a typical translation might be:

ldr   r3, =ucRTUBuf         ; load base address
ldrh  r2, =usRcvBufferPos   ; load index (16-bit)
ldrh  r2, [r2]
add   r3, r3, r2            ; r3 = &ucRTUBuf[usRcvBufferPos]
strb  r1, [r3]              ; store ucByte (r1)
add   r2, r2, #1
strh  r2, =usRcvBufferPos

It should not involve loading from a pointer stored in memory unless the buffer is accessed via a pointer variable — but in your case it's a statically declared array.

 

--::--

 

Note that the workaround indeed involves an  "ldr , =ucRTUBuf" instruction:

 

#ifdef STM32_CMAKE // work around nasty gcc compiler bug volatile UCHAR* destPtr; __asm__ volatile ("ldr %0, =ucRTUBuf" : "=r" (destPtr)); destPtr += usRcvBufferPos; *destPtr = ucByte; usRcvBufferPos++; #else ucRTUBuf[usRcvBufferPos++] = ucByte; #endif

 

 

Pavel A.
Super User

ucRTUBuf is a statically allocated array, and not a pointer, meaning it should be accessed directly — not via an indirect pointer dereference

Consider that THUMB instruction set has some limitations vs. 32-bit ARM. This may explain the indirect access via registers. 

The compiler can use any suitable way to skin a cat (pardon me cat lovers).

Besides of alignment, there can be also aliasing issues (implicit assumptions that function parameters passed by pointers are not overlapping, etc.). Try to write good, standard C (ChatGPT can definitely help with this when it's not in hallucination mood). Inline assembly makes your code hard to understand and maintain by a human.

 

 

 

 

@Pavel A. 

The problem is that a/o this simple standard C causes a hard fault in my project:

// global #define MB_SER_PDU_SIZE_MAX 256 volatile UCHAR ucRTUBuf[MB_SER_PDU_SIZE_MAX]; static volatile USHORT usRcvBufferPos; //local UCHAR ucByte; ucRTUBuf[usRcvBufferPos++] = ucByte;

 

The inline assembly is only there in order to work around these problems.

TDK
Super User

If you want to track it down, consider submitting a proper bug report to the GCC project. Nothing to be done here.

GCC Bugs - GNU Project

If you feel a post has answered your question, please click "Accept as Solution".
Pavel A.
Super User

I suspect that the CMAKE build uses wrong compiler or linker options, bad link time code generation and so on. The toolchain is known to be pretty stable when used correctly.