Access to struct on unaligned address with -Os option on Cortex-M0 produces hard fault

Asked by Andre Heßling

I noticed a problem using the -Os optimization on a Cortex-M0 architecture (STM32F0xx to be precise).

The following struct is defined in the code:

typedef struct
{
    uint8_t data[31];
    uint8_t crc;
} sample_struct_t;

Then a variable of the struct type is defined:

volatile sample_struct_t sample_struct;

The address of the struct variable is later passed to a function:
uint32_t CRC_val = CRC_CalcBlockCRC((uint32_t*)&sample_struct, sizeof(sample_struct_t) / sizeof(uint32_t));
Prototype: uint32_t CRC_CalcBlockCRC(uint32_t pBuffer[], uint32_t BufferLength);

When the function is called, the MCU goes to Hard Fault. The reason is that the variable is on an unaligned address (as seen in the map file).
0x2000023b sample_struct

The compiler produces the instruction "ldmia r0!, {r2}" with r0 = 0x2000023b in the function CRC_CalcBlockCRC.
This instruction leads to the Hard Fault condition which is correct according to the ARM specification.

If -O2 is enabled instead of -Os, the variable is mapped to 0x2000023c (aligned) and the code works as expected.

In my opinion the compiler should know that a Cortex-M0 architecture cannot perform unaligned memory access operations and therefore should not attempt to map this structure to an unaligned address. The compiler option "-mno-unaligned-access" has no influence. In a similar question (https://answers.launchpad.net/gcc-arm-embedded/+question/226405) I found that maybe adding -fno-strict-volatile-bitfields may be a workaround but still it has no influence.

The command line of the compiler is:
arm-none-eabi-gcc -c -Ifwlib -Isrc -DHSE_VALUE=8000000 -DUSE_STDPERIPH_DRIVER -DCP_TARGET=CP_CC_STM32F0xx -Os -std=gnu99 -gdwarf-2 -ffunction-sections -fdata-sections -Wall -Wextra -Wa,-adhlns=obj/main.lst -fsigned-char -c -fmessage-length=0 -fno-strict-aliasing -fno-strict-volatile-bitfields -mcpu=cortex-m0 -mthumb -g3 -gdwarf-2 -mno-unaligned-access -MMD -MP -MF obj/main.d src/main.c -o obj/src/main.o

The command line of the linker is:
arm-none-eabi-gcc [objects...] -Wl,-Map=obj/Test.map,--cref -Wl,--gc-sections -nostartfiles -Xlinker -T"stm32_flash.ld" -mcpu=cortex-m0 -mthumb -g3 -gdwarf-2 -mno-unaligned-access --output obj/Test.elf

I am using 4.7-2013q3.

Question information

Language:
English Edit question
Status:
Answered
For:
GNU Arm Embedded Toolchain Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Terry Guo (terry.guo) said :
#1

This is a known issue and has been discussed some times. The key point is code (uint32_t*)&sample_struct, you are converting something with smaller alignment requirement to something with bigger alignment requirement. If original value is aligned to the bigger boundary, then such conversion is safe. Otherwise it is dangerous because the result with type (uint32_t *) is always regarded as 32bit aligned either from C language view or from compiler view.

IMHO, the better way is to redefine the type of your sample_struct to ensure its natural alignment is 32bit.

Revision history for this message
Carsten Schumann (carstnn) said :
#2

Hi Terry,

I mostly agree with your statement that a conversion like (uint32_t*)&sample_struct is dangerous as the compiler (especially when dealing with externals) cannot know how the variable is stored and which alignment is dealt with. As the Cortex-M0 only supports aligned access, there is also no easy workaround for that issue by using other assembler instructions.

On the other hand I am a little confused how the linker places the struct within the address space. As the struct (seen as a memory block) has >=32 bit size and is dividable by 32bit, I would expect the struct starting at a 4-byte boundary. Also I would expect that using the -mno-unaligned-access compiler option would force all variables with >=4 bytes to start at 4-byte boundaries:

[cite]
-munaligned-access
-mno-unaligned-access
Enables (or disables) reading and writing of 16- and 32- bit values from addresses that are not 16- or 32- bit aligned. By default unaligned access is disabled for all pre-ARMv6 and all ARMv6-M architectures, and enabled for all other architectures. If unaligned access is not enabled then words in packed data structures will be accessed a byte at a time.
[/cite]

Is there any way to force such a behavior without writing an __attribute.... to each variable?

Carsten

Revision history for this message
Terry Guo (terry.guo) said :
#3

Hi Carsten,

I think I can answer your first question at the moment. The struct defined as

typedef struct
{
    uint8_t data[31];
    uint8_t crc;
} sample_struct_t;

has 1-byte natural alignment because each of its members are simple unsigned char type. For me, this is something like "unsigned char x[32]", no 4-byte alignment limitation. For case above, there must be "char" or "unsigned char" type data in data section which causes the struct is placed on an address which isn't 4-byte aligned.

For the option -munaligned-access, I need to double-check the gcc code.

For the last question, I don't think there is such way.

Revision history for this message
Joey Ye (jinyun-ye) said :
#4

-mno-unaligned-access only handle cases that compiler knows an address is
unaligned. In this case, compiler failed to know this as int* appeared to
be aligned. C compiler doesn't guarantee such a conversion works.
On Nov 13, 2013 9:41 PM, "Terry Guo" <email address hidden>
wrote:

> Question #239149 on GCC ARM Embedded changed:
> https://answers.launchpad.net/gcc-arm-embedded/+question/239149
>
> Terry Guo proposed the following answer:
> Hi Carsten,
>
> I think I can answer your first question at the moment. The struct
> defined as
>
> typedef struct
> {
> uint8_t data[31];
> uint8_t crc;
> } sample_struct_t;
>
> has 1-byte natural alignment because each of its members are simple
> unsigned char type. For me, this is something like "unsigned char
> x[32]", no 4-byte alignment limitation. For case above, there must be
> "char" or "unsigned char" type data in data section which causes the
> struct is placed on an address which isn't 4-byte aligned.
>
> For the option -munaligned-access, I need to double-check the gcc code.
>
> For the last question, I don't think there is such way.
>
> --
> You received this question notification because you are an answer
> contact for GCC ARM Embedded.
>

Can you help with this problem?

Provide an answer of your own, or ask Andre Heßling for more information if necessary.

To post a message you must log in.