Skip to main content
Explorer II
December 13, 2023
Question

Hard fault caused by unaligned access

  • December 13, 2023
  • 12 replies
  • 12574 views

Hi, 

I have an issue when trying to copy a struct by direct assignment on STM32H743; 

The code: 

 

 

void function(const structName_t *incomingStruct) {

Signal_t sendSignal;

sendSignal.desiredStruct = *incomingStruct; // <-- Causes unaligned access

..

}

 

 

If desiredStruct consists of only 8 or 16 bits fields, everything works perfectly. If however, one or more of the fields is 32-bits, then the SCB->CFSR->UNALIGNED bit goes high and the programs enters the hard_Fault handler after line 5. I do have a quick fix on this; declaring the desiredStruct as __attribute__((__packed__)). However, as other projects running on STM32F7 use the same library without any issues, I would like to know if anybody has a local solution, or any idea of what's going on. As I understand it, it is the compilers job to add the desired padding to the data to have it aligned, but it doesn't look like it's able to do so? I would assume the compiler ideally don't want any packed attribute, as we remove it's freedom to alter the space between each structure field. I would love to have more input on this specific problem :)

Thanks

 

EDIT: Copying each struct field one by one also works, it is only the operation above that causes trouble. 

    This topic has been closed for replies.

    12 replies

    Graduate
    December 13, 2023

    Show the declarations of both structures. It may have something to do with 64-bit data present in one of them.

    RBrec.1Author
    Explorer II
    December 13, 2023

    Kinda easier said than done.. Initially structName_t was

    typedef struct {
     int32_t field1;
     int32_t field2;
     int32_t field3;
     int32_t field4;
     int32_t field5;
     int32_t field6;
    }structName_t;

     Which didn't work, so under debugging I found that if even one of them was 32-bit, it failed. Which one doesn't matter. The Signal_t struct however is a mess and would require a lot of rewriting, as it is kinda sensitive information. Obviously hard to help if I throw that at you, but it does include a bunch of sub-structs that again includes 8, 16, 32 bits and a lot of enums. Either of the enums have values above 32-bits, so I assume the size of the fields then would have a max size of 32? Could you maybe elaborate what you mean by 64-bit data present somewhere?

    Super User
    December 13, 2023

    > Show the declarations of both structures.

    Not only that, but also location in memory (possibly the mapfile), and disasm of the relevant portion of code.

    The compiler has decided to use load double (as unaligned 32-bit accesses would be OK), but, as you've said:

    > compilers job to add the desired padding to the data to have it aligned

    so there's probably some switch or attribute which misguided the compiler.

    JW

    Graduate
    December 13, 2023

    The problem may arise if the other structure has some packing options or 64-bit data, so we cannot proceed without the other structure declaration.

    RBrec.1Author
    Explorer II
    December 13, 2023

    @gbm @waclawek.jan  I am currently looking into how chaos such a file would be, as the struct is quite large. In the meantime, I notice there are some structs that has the following struct

     

     

    typedef enum {
     ..
     ..
     LAST = 124,
    } Enum_t;
    
    typedef struct
    {
     Enum_e enum;
     uint8_t config1;
     uint8_t config2;
     uint8_t config3;
     uint32_t config4;
     uint8_t config5;
    } SubConfig_t;
    
    #define NUMBER_OF_SUB_CONFIG 10
    
    typedef struct
    {
     SubConfig_t config[NUMBER_OF_SUB_CONFIG];
    } Config_t;

     

     

    where Config_t is a field in Signal_t. This seems to be the most complex field of Signal_t. Just wonder if the list is somehow messing with data alignment?

    The unaligned issue seems to have been a problem earlier, as one other struct also has the packed attribute (Couldn't get any other explanation on the problem at the time other than that the packed structure worked :clapping_hands::clapping_hands:)  Removing this just ends up with the hard_Fault happening on a copy even earlier. No other instances of attributes to the structs. 

    Graduate
    December 13, 2023

    The probable source of the problem is that structure with the packed attribute. With GCC, "packed" is not exactly what we usually assume it to be. GCC "packed" structure is not really packed; it's only shortened at the end.

    Your quick fix caused the compiler to abandon the idea of using long, aligned accesses for structure copy - that's why it worked. It didn't fix the problem, it just masked it.

    Super User
    December 13, 2023

    > With GCC, "packed" is not exactly what we usually assume it to be. GCC "packed" structure is not really packed; it's only shortened at the end.

    I've never heard of this, and my experience is different, individual members of non-packed structs being word-aligned (i.e. appropriate spacers inserted by gcc).

    #include <stdint.h>
    
    struct {
     uint8_t byte;
     uint16_t halfword;
     uint32_t word;
    } a;
    
    struct __attribute__((packed)) {
     uint8_t byte;
     uint16_t halfword;
     uint32_t word;
    } b;
    
    volatile uint32_t x;
    
    int main(void) {
     a.byte = x;
     a.halfword = x;
     a.word = x;
    
     b.byte = x;
     b.halfword = x;
     b.word = x;
    
     while(1);
    }
    arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 4.8.3 20131129 (release) [ARM/embedded-4_8-branch revision 205641]
    Copyright (C) 2013 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
    Compiling: dl.c
    d:/PROGRA~1/ARMTools/launchpad.net_gcc-arm-embedded/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -I. -gdwarf-2 -DROM_RUN -DSTM32F407xx -O3 -Wall -Wstrict-prototypes -Wcast-align -Wcast-qual -Wimplicit -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wpointer-arith -Wswitch -Wredundant-decls -Wreturn-type -Wshadow -Wstrict-prototypes -Wunused -Wa,-adhlns=objdir/dl.lst -ILibraries/STM32F4xx_StdPeriph_Driver/inc/ -std=gnu99 -g3 -MD -MP -MF .dep/dl.o.d dl.c -o objdir/dl.o
    
    Linking: dl.elf
    d:/PROGRA~1/ARMTools/launchpad.net_gcc-arm-embedded/bin/arm-none-eabi-gcc -mthumb -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -I. -gdwarf-2 -DROM_RUN -DSTM32F407xx -O3 -Wall -Wstrict-prototypes -Wcast-align -Wcast-qual -Wimplicit -Wmissing-declarations -Wmissing-prototypes -Wnested-externs -Wpointer-arith -Wswitch -Wredundant-decls -Wreturn-type -Wshadow -Wstrict-prototypes -Wunused -Wa,-adhlns=objdir/startup_stm32f4xx.o -ILibraries/STM32F4xx_StdPeriph_Driver/inc/ -std=gnu99 -g3 -MD -MP -MF .dep/dl.elf.d objdir/startup_stm32f4xx.o objdir/dl.o --output dl.elf -nostartfiles -Wl,-Map=dl.map,--cref -lm -l:stm32f4_stdlib.a -Tstm32_flash.ld
    int main(void) {
     a.byte = x;
     8000204:	4b09 	ldr	r3, [pc, #36]	; (800022c <main+0x28>)
     8000206:	490a 	ldr	r1, [pc, #40]	; (8000230 <main+0x2c>)
     a.halfword = x;
     a.word = x;
    
     b.byte = x;
     8000208:	4a0a 	ldr	r2, [pc, #40]	; (8000234 <main+0x30>)
     uint32_t word;
    } b;
    
    volatile uint32_t x;
    
    int main(void) {
     800020a:	b4f0 	push	{r4, r5, r6, r7}
     a.byte = x;
     800020c:	681f 	ldr	r7, [r3, #0]
     a.halfword = x;
     800020e:	681e 	ldr	r6, [r3, #0]
     a.word = x;
     8000210:	6818 	ldr	r0, [r3, #0]
    
     b.byte = x;
     8000212:	681d 	ldr	r5, [r3, #0]
     b.halfword = x;
     8000214:	681c 	ldr	r4, [r3, #0]
     b.word = x;
     8000216:	681b 	ldr	r3, [r3, #0]
    } b;
    
    volatile uint32_t x;
    
    int main(void) {
     a.byte = x;
     8000218:	700f 	strb	r7, [r1, #0]
     a.halfword = x;
     800021a:	804e 	strh	r6, [r1, #2]
     a.word = x;
     800021c:	6048 	str	r0, [r1, #4]
    
     b.byte = x;
     800021e:	7015 	strb	r5, [r2, #0]
     b.halfword = x;
     8000220:	f8a2 4001 	strh.w	r4, [r2, #1]
     b.word = x;
     8000224:	f8c2 3003 	str.w	r3, [r2, #3]
     8000228:	e7fe 	b.n	8000228 <main+0x24>

     

    JW

    Technical Moderator
    December 13, 2023

    Hello,

    Just for your info, from PM0253:

    SofLit_0-1702464790002.png

    So look at your generated assembly code and check if there is no instruction outside the list above then check the UNALIGN_TRP bit in SCB_CCR register. If it's set, reset it.

    RBrec.1Author
    Explorer II
    December 13, 2023

    @mƎALLEm Looking at the specific instructions when running the line above:

    assembly.JPG

    It is the bottom line that fails. Further details the UNALIGN_TRP bit is zero. I guess receiving confirmation that an illegal unaligned access is performed was kinda dissatisfying, because my main question becomes why the compiler would allow such an instruction. But I guess this problem at least is of the hook for ST :grinning_face_with_sweat: 

    Super User
    December 13, 2023

    How exactly do you call doSomething()? I am interested in the way how do you determine the second, sendData parameter. Don't you typecast a pointer taken from a pool of bytes (communication buffer),?

    JW

    RBrec.1Author
    Explorer II
    December 13, 2023

    Maybe easier to write in code (I see sendData is a pretty *** name..)

    static Config_t ConfigData;
    
    createSignalHandler(doSomething, &ConfigData, QUEUE_CONFIG, ...);

    So sending a signal to QUEUE_CONFIG which will trigger doSomething() with the signal itself and the static global struct ConfigData. So in the previous example, doSomething() is triggered by Signal_t configSignal sent to QUEUE_CONFIG, and is executed with ConfigData and configSignal as parameter 1 and 2. Hope this makes sense.

    Super User
    December 13, 2023

    So the failing instruction is ldmia. Why does it require alignment? Is the *incomingStruct located in normal memory?

     

    RBrec.1Author
    Explorer II
    December 13, 2023

    Why does it require alignment: I guess because the hw doesn't allow unaligned ldmia instructions

    Is the *incomingStruct located in normal memory: Yes.

    As been mentioned above, there was one previous struct which was also declared as packed. So the original question could potentially be a result by the previous packed that may have caused unalignment somehow. However, having zero packed structs still causes the original problem (fixed ages ago with the packed attribute), so now I kinda try to understand if something can fix the original problem, and hopefully this also fixes the question from the beginning.. 

    Graduate
    December 13, 2023

    Do you ever cast a pointer to this struct form a pointer to byte/halfword? It usually occurs when the data is received via comm interface and stored in a byte buffer or read from non-volatile memory as an array of bytes.

    Super User
    December 13, 2023

    I am interested in the way how do you determine the second, sendData parameter. Or, as you say above, configSignal (I don't see where does it come from).

    Don't you typecast a pointer taken from a pool of bytes (communication buffer),?

    If yes, then that's the problem. The compiler itself is innocent. The C standard says this (C99, 6.3.2.3#7):

    A pointer to an object or incomplete type may be converted to a pointer to a different
    object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined.

    However, in practice, converting between structs and arrays of bytes is a common practice, as this is what often happens when data are transmitted or stored. There are several "schools" telling how to do this "correctly", and these influence and are influenced by particular compilers. The prevalent view in the near past was, that you should use unions; today the view shifted in favour of copying structs from/to byte arrays using memcpy. It appears, that in the gcc world, using packed structs (which are needed for precise control of layout for communication anyway) results in compiler being much more careful in accessing them, than without; however, it's a gcc extension.

    JW

     

    Super User
    December 13, 2023

    .

    Super User
    December 13, 2023

    What you quoted is some assembler guide, not architectural documentation. Does that assembler support Cortex-Mx at all?

    JW