2

I'm debugging a piece of code that is causing some crashes using Keil's compiler but not ARM-GCC. I'm not familiar with the history of the code but as I'm reading more I'm realizing that there is definitely at least a strict-aliasing violation in the code. However the error that drew me to the code in the first place was an unaligned access fault when running this snippet.

I'm embarassed to admit that I was not very aware of the issues with this kind of strict aliasing violation, and would like to convince myself that fixing that will solve the issue, and not just mask whatever is causing the unaligned fault.

  1. Could someone help me understand why I'm seeing the unaligned access fault? Is this related to the strict-aliasing violation and if so, how does that manifest as such?

  2. What would the best way to resolve this be? I have declared sampleStruct as __packed now which appears to be working but would using a union for sampleStruct be a better solution? Or going further would I need to copy individual members of the struct?

I'm still learning more about the strict-aliasing rules, but a push in the right direction would be much appreciated.

*Note: This is running on in an embedded system on a Cortex-M4 and will never run on another platform or hardware.

Disassembly:

    0x00031DF0 9802      LDR           r0,[sp,#0x08]
        64:             structA->SessionCount = params.SessionCount; 
    0x00031DF2 60A0      STR           r0,[r4,#0x08]
        65:             structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f; 
    0x00031DF4 9806      LDR           r0,[sp,#0x18]
        65:             structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f; 
    0x00031DF6 B140      CBZ           r0,0x00031E0A
        65:             structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f; 
    0x00031DF8 EDDD0A05  VLDR          s1,[sp,#0x14]
    0x00031DFC ED9D0A06  VLDR          s0,[sp,#0x18]
    0x00031E00 EEB81A40  VCVT.F32.U32  s2,s0
    0x00031E04 EE800A81  VDIV.F32      s0,s1,s2
    0x00031E08 E001      B             0x00031E0E
        65:             structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f; 
    0x00031E0A ED9F0A1E  VLDR          s0,[pc,#0x78]
>   0x00031E0E ED840A03  VSTR          s0,[r4,#0x0C]
        66:             structA->Value = params.ValueLast; 
    0x00031E12 ED9D0A04  VLDR          s0,[sp,#0x10]
    0x00031E16 ED840A04  VSTR          s0,[r4,#0x10]
        67:             structA->SessionValueLow = params.ValueLow; 
    0x00031E1A ED9D0A07  VLDR          s0,[sp,#0x1C]
    0x00031E1E ED840A05  VSTR          s0,[r4,#0x14]
        68:             structA->SessionValueHigh = params.ValueHigh; 
    0x00031E22 ED9D0A08  VLDR          s0,[sp,#0x20]



58  case SESSION_INFO_HDL: {
59      AppParams_t params;
60      AppParamsRead(&params);
61      sampleStruct_t *structA      = (sampleStruct_t *) &pData->pValue[offset];
62      structA->TotalCount          = params.TotalCount;
63      structA->SessionId           = params.SessionId;
64      structA->SessionCount        = params.SessionCount;
65      structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f;
66      structA->Value               = params.ValueLast;
67      structA->SessionValueLow     = params.ValueLow;
68      structA->SessionValueHigh    = params.ValueHigh;
69      structA->Reserved = 0;
70      AttsSetAttr(SESSION_INFO_HDL, sizeof(*structA), &pData->pValue[offset]);
71      break;
72  }

Code Snippet: https://godbolt.org/z/Djebj2

typedef struct
{
    uint8_t           *pValue;          /*!< \brief Pointer to the data's value */
    uint16_t          *pLen;            /*!< \brief Pointer to the length of the data's value */
} data_t;

typedef struct sampleStruct {
    uint32_t TotalCount;
    uint32_t SessionId;
    uint32_t SessionCount;
    float    SessionValueAverage;
    float    Value;
    float    SessionValueLow;
    float    SessionValueHigh;
    uint32_t Reserved;
} sampleStruct_t;

typedef struct AppParams {
    uint32_t TotalCount;
    uint32_t SessionId;
    uint32_t SessionCount;
    uint32_t CalibrationThreshold;
    float    ValueLast;
    float    ValueTotal;
    uint32_t ValueNum;
    float    ValueLow;
    float    ValueHigh;
} AppParams_t;


void function ( uint16_t offset, data_t * pData )
{
    AppParams_t params;

    sampleStruct_t *structA      = (sampleStruct_t *) &pData->pValue[offset];
    structA->TotalCount          = params.TotalCount;
    structA->SessionId           = params.SessionId;
    structA->SessionCount        = params.SessionCount;
    structA->SessionValueAverage = params.ValueNum != 0 ? params.ValueTotal / params.ValueNum : 0.0f;
    structA->Value               = params.ValueLast;
    structA->SessionValueLow     = params.ValueLow;
    structA->SessionValueHigh    = params.ValueHigh;
    structA->Reserved = 0;
    AttsSetAttr(SESSION_INFO_HDL, sizeof(*structA), &pData->pValue[offset]);

    send_data ( SESSION_INFO_HDL, &pData->pValue[offset], sizeof(*structA) );
}
Otus
  • 305
  • 1
  • 5
  • 16

2 Answers2

2

Your problem is in this line:

sampleStruct_t * structA = (sampleStruct_t *) &pData->pValue[offset];

pData->pValue is a pointer to uint8_t - that is, it can point to any byte in memory. From that, &pData->pValue[offset] is also a pointer to any (unaligned) byte in memory. The typecast and assignment makes structA then a pointer to any unaligned byte in memory. After that, you do this:

structA->intA   = get_int_A();

Which can attempt to make an unaligned store of a uint32_t through structA->intA.

If you want to fix this problem, you need to ensure the alignment of the pointers you're casting into types that expect particular alignment.

Declaring your structure packed works around the problem by avoiding the implicit alignment requirement of your structure.

Carl Norum
  • 219,201
  • 40
  • 422
  • 469
  • Thanks, that made a lot more sense out of the alignment issue. Am I correct in saying that there is also an strict-aliasing violation here? From what I understand of the rule, two different data types should never alias the same memory. In my case that's the sampleStruct_t * and the uint8_t * aliasing the same buffer. – Otus Jan 04 '20 at 16:56
  • Yes - that's right. The byte buffer could alias your structure, but not the other way around. https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule – Carl Norum Jan 04 '20 at 17:00
  • @CarlNorum this answer is rather general not CORTEX M4 related. – 0___________ Jan 04 '20 at 18:06
  • @CarlNorum Cortex M4 supports unaligned access. Only some (less frequently used by the compiler) support only aligned access – 0___________ Jan 04 '20 at 18:08
1

Cortex M4 support unaligned access for the following instructions:

•LDR, LDRT•LDRH, LDRHT•LDRSH, LDRSHT•STR, STRT•STRH, STRHT

It is very unlikely that the code you posted (which will not even compile) will use instructions which do not accept unaligned access.

The amended version of your code (just to make it compile) https://godbolt.org/z/X_77Bc

So why do you get the HF-s.

Two possible answers.

  1. UNALIGN_TRP bit in the "Configuration and Control Register" is set. If it is set any unaligned access will trigger the exeption

  2. The code you do not show uses LDM, STM, LDRD or STRD instructions which always HF if access is unaligned

0___________
  • 60,014
  • 4
  • 34
  • 74
  • I have updated the question with code that's closer to what I'm actually running and does compile. I have checked the UNALIGNED_TRP bit is not set according to my debugger, and the instruction that branches to the fault handler is the indicated VSTR above. Seems like this shouldn't be happening then. – Otus Jan 04 '20 at 19:01
  • It is because of the math. your compiler uses FPU and FPU instructions require aligned access – 0___________ Jan 04 '20 at 22:04