Device crash on dereferencing pointer to struct. Works in simulator

I have a C project for Playdate and encountered crashes on the device, but not in the simulator.

I distilled the problem to the following code, which reproduces the crashes:
Attached project

typedef uint32_t MapDimension;

typedef struct
{
	MapDimension x;
	MapDimension y;
	MapDimension z;
}MapPosition;
size_t bufferSize = 100;
size_t offset = 1;

void* buffer = pd->system->realloc(NULL, bufferSize);
memset(buffer, 0, bufferSize);

MapPosition* mp = (char*)buffer + offset;
mp->x = 1;
mp->y = 2;
mp->z = 3;

// if MapDimension is defined as uint32_t
// This will crash device if offset is 1,2,3
// This will not crash device if offset is 0,4,8,12 ...
MapPosition mpCopy = *mp;

// if MapDimension is defined as uint16_t or uint8_t it works as intended.
// In simulator everything always works.

I am working on Windows and using SDK Version 2.5.0.

What am I missing? I saw couple of threads about crashes on device and not in simulator could this be piece of puzzle?

Looking forward to your response.

It looks like what you’re missing is alignment, although I don’t know offhand what the alignment requirements of the Playdate CPU are. Is there a particular reason why you want to access a structure at offset 1 byte in your buffer?

In various data structures, I allocate chunk of heap memory and manage data there. It seemed perfectly reasonable.

I will try to take into account alignment.

We can confirm that it's an alignment fault by checking the CFSR flags in the crash report in the crashlog.txt file. When I run your code I get cfsr:01000000 which How to debug a HardFault on an ARM Cortex-M MCU | Interrupt says is indeed the UNALIGNED flag; and they mention briefly that

Unaligned multiple word accesses, such as accessing a uint64_t that is not 8-byte aligned, will always generate this fault.

We have the UNALIGN_TRP flag turned off so that unaligned access is allowed, so this surprised me. I didn't know about that multiple word exception. Here's a bit more detail from Documentation – Arm Developer

Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of the setting of the UNALIGN_TRP bit.

And sure enough, the disassembler shows that in your code mpCopy = *mp; compiles to

   0x000000be <+46>:	ldmia	r2, {r0, r1, r2}
   0x000000c0 <+48>:	stmia.w	r3, {r0, r1, r2}

that is, it loads the values at $r2, $r2+4, and $r2+8 into the $r0, $r1, and $r2 registers respectively, then stores them back into $r3, $r3+4, and $r3+8. I've tried to find a compiler option that tells it not to use ldm/stm in this case but no luck--not surprising really, since the C spec requires that structs be aligned to the size of their largest component type.

It looks like memcpy will do what you want here, though, since that doesn't have any alignment assumptions. If I do memcpy(&mpCopy, mp, sizeof(MapPosition)); instead I get str/ldr instructions which work fine, just a bit slower, with unaligned data.

2 Likes

It's that you're dereferencing a pointer that's unaligned that's causing problems. Unaligned pointer dereferencing is undefined behavior, and "seems to work correctly over here" is a subset of undefined behavior :slight_smile: