Hard faults due to (gcc) compiler computing incorrect addresses
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 5:15 AM
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
#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
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.
- Labels:
-
VSCode for STM32
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 6:21 AM - edited ‎2025-04-29 6:22 AM
> 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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 6:34 AM
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 6:38 AM
@Ozone If it were an alignment issue, it could not be worked around using inline assembly.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 7:25 AM - edited ‎2025-04-29 9:29 AM
@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:
In the current highlighted line, r4 has a value of 0x20000104, while r3=0.
Stepping through, we get a value of 0xf0006008 in r3:
At this point, the next statement (strb r1,[r3,r2]) causes a hard fault writing to 0xf0006008.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 9:00 AM - edited ‎2025-04-29 9:31 AM
Done some further analysis using ChatGPT using an earlier screenshot of the same c code been disassembled:
--::--
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 10:02 AM - edited ‎2025-04-29 10:08 AM
> 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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 10:11 AM
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.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 10:16 AM
If you want to track it down, consider submitting a proper bug report to the GCC project. Nothing to be done here.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
‎2025-04-29 10:42 AM
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.
