11

I am running into an Hard Fault Exception while copying some data on a micro-controller from one struct to another. I tried different implementations which should do all the same. See my code lines:

memcpy(&msg.data, data, 8);
memcpy(&msg.data, data, sizeof(*data));
memcpy(&msg.data, data, sizeof(msg.data));
msg.data = *data;  // Hard Fault

The first three lines are working pretty fine. The last one ends with an Hard Fault Exception. The assembly for the lines with memcpy is the same. The assembly for the direct assignment differs:

  memcpy(&msg.data, data, sizeof(msg.data));
 800c480:   f107 030c   add.w   r3, r7, #12
 800c484:   330b        adds    r3, #11
 800c486:   2208        movs    r2, #8
 800c488:   6879        ldr r1, [r7, #4]
 800c48a:   4618        mov r0, r3
 800c48c:   f7f4 f82e   bl  80004ec <memcpy>
  msg.data = *data;                  // Hard Fault
 800c490:   687b        ldr r3, [r7, #4]
 800c492:   f107 0217   add.w   r2, r7, #23
 800c496:   cb03        ldmia   r3!, {r0, r1}
 800c498:   6010        str r0, [r2, #0]
 800c49a:   6051        str r1, [r2, #4]

I am using the GNU Arm Embedded Toolchain 5.4.1 20160919.

Here is a minimal code example which (hopefully) shows the the problem. The data structure msg_t must use the packed attribute to match some hardware registers. On the micro-controller this codes ends in a Hard Fault at the line with msg.data = *data;

#include <stdint.h>
#include <string.h>
#include <stdio.h>

typedef struct canData_s {
  uint8_t d1;
  uint8_t d2;
  uint8_t d3;
  uint8_t d4;
  uint8_t d5;
  uint8_t d6;
  uint8_t d7;
  uint8_t d8; 
} canData_t;

#pragma pack(push, 1)
typedef struct msg_s {
  uint32_t stdId;
  uint32_t extId;
  uint8_t ide;
  uint8_t rtr;
  uint8_t dlc;
  canData_t data;  // 8 Bytes
  uint8_t navail;  // not available
  uint32_t timestamp;
} msg_t;
#pragma pack(pop)

void setData(canData_t *data) {
  msg_t msg;
  msg.data = *data;

  // Do something more ...
  printf("D1:%d", msg.data.d1);
  // ...
}

int main() {
  canData_t data;
  memset(&data, 0, 8);

  setData(&data);
}

Why does copying the structure by a direct assignment fail?

eDeviser
  • 1,605
  • 2
  • 17
  • 44

2 Answers2

10

When you use non-standard #pragma pack you force the compiler to store the struct without any padding. The struct members before data are in groups of 4+4+3, then data at byte 11, which is misaligned.

So you force data to always be allocated misaligned, which can cause hardware exceptions on some CPUs if this is accessed as a word (32 bits). The code msg.data = *data; generated by the compiler might assume that when you copy two structs, they are always properly aligned, as that's normally the case. And the most efficient implementation of the copy would work with 32 bit chunks of data, so that's what it will use.

The question here is why this struct is packed to begin with, since it can neither be a hardware register mapping nor a data protocol mapping. Things like CAN-bus IDE and RTR are just single bits; I very much doubt any CAN controller reserves a whole 8 bit register for that. For example ST's "bxCAN" controller place these as individual bits in the CAN_TIxR register (CAN TX mailbox identifier register). Every other CAN controller on the market will behave similarly.

As for the CAN frame itself, you cannot directly memory-map that. The CAN controller will grab the raw CAN frame and place it in its own memory-mapped registers.

Either re-make this struct without padding or use the actual CAN controller registers as provided by your hardware.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • How is `struct` full of `uint8_t` misaligned? I always thought packing would relax the alignment restrictions by forcing compiler to emit more inefficient but more forgiving output. – user694733 Feb 12 '18 at 12:24
  • @user694733 Because whatever copy code the compiler generated seems to be written in an optimized way that utilizes 32 bit read/writes. – Lundin Feb 12 '18 at 12:28
  • Added some clarifications to the answer. – Lundin Feb 12 '18 at 12:39
  • @Lundin another problem - if the struct is packed the compiler should generate the less efficient but safe code for it as gcc does - using the byte instructions instead. This one does not, which is ridiculous.. BTW what is this compiler? – 0___________ Feb 12 '18 at 12:43
  • Yes you are right. I took a look to the CAN API which shows that the structure is not based on the hardware. It seems more that it is based on the author of the ST-Standard-Library. – eDeviser Feb 12 '18 at 12:56
0

I found out that there is a CFSR register which contains information about the type of the Hard Fault Exception. The register shows that the bit no 24 is set. ARM's programming manual PM0214 says at page 221:

Bit 24 UNALIGNED: Unaligned access usage fault. Enable trapping of unaligned accesses by setting the UNALIGN_TRP bit in the CCR to 1, see Configuration and control register (CCR) on page 214. Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of the setting of UNALIGN_TRP.

0: No unaligned access fault, or unaligned access trapping not enabled

1: The processor has made an unaligned memory access.

This really matches to @Lundin's answer.

eDeviser
  • 1,605
  • 2
  • 17
  • 44
  • 1
    Change (update) the compiler. Decent one should when assigning the structures generate the safe code. For example gcc generates the byte wide load store instructions or calls internally the memcpy if there is a risk of the unaligned struct data access. – 0___________ Feb 13 '18 at 13:17
  • Hello @PeterJ_01. I Just updated my compiler to 7.2.1 but this did not solve the problem. :-( – eDeviser Mar 21 '18 at 07:36