Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting pointers on embedded devices

I encountered a strange problem when casting and modifying pointers on a 32bit embedded system (redbee econotag running contiki OS to be specific).

uint32_t array[2];
array[0] = 0x76543210;
array[1] = 0xfedcba98;

uint8_t* point = ((uint8_t*)array)+1;

printf("%08x \n", *(uint32_t*)point );

output on my computer:

98765432

output on embedded device:

10765432

My computer behaves as I expect it to, the embedded device however seems to wrap around when it reaches the end of the word. Why does this happen?

like image 786
Baarn Avatar asked Dec 25 '12 16:12

Baarn


5 Answers

Your target "redbee econotag" is stated as an ARM7 which has ARMv4 architecture. ARMv4 doesn't provide unaligned memory access like an ARMv7 or an intel machine.

Quoting from ARM's documentation:

On ARMv4 and ARMv5 architectures, and on the ARMv6 architecture depending on how it is configured, care needs to be taken when accessing unaligned data in memory, lest unexpected results are returned. For example, when a conventional pointer is used to read a word in C or C++ source code, the ARM compiler generates assembly language code that reads the word using an LDR instruction. This works as expected when the address is a multiple of four, for example if it lies on a word boundary. However, if the address is not a multiple of four, the LDR returns a rotated result rather than performing a true unaligned word load. Generally, this rotation is not what the programmer expects.

like image 76
auselen Avatar answered Oct 20 '22 15:10

auselen


With this code, you break the strict aliasing rule: the object pointed by point is accessed by an lvalue expression that has uint32_ttype.

C11 (n1570), § 6.5 Expressions
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type.

This leads to an undefined behavior, so anything can happen.

C11 (n1570), § 4. Conformance
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint or runtimeconstraint is violated, the behavior is undefined.

like image 34
md5 Avatar answered Oct 20 '22 14:10

md5


Because of the +1 you do an unaligned access of a 32-bit value, i.e. the address is not a multiple of four.

x86 works independently of alignment, because its roots go all the way back to 8-bit machines (maybe performance is slightly worse).

ARM requires alignment (as do many other processors), so 32-bit values should be placed at addresses that are a multiple of four bytes. Various bad things may happen if this isn't the case (wrong values, faults). For the array the compiler takes care of that, but when you explicitly cast the pointer you force it to violate alignment.

like image 33
starblue Avatar answered Oct 20 '22 14:10

starblue


printf("%08x \n", *(uint32_t*)point );

The * expression in this statement invokes undefined behavior: it violates aliasing rules and may do unaligned access.

like image 5
ouah Avatar answered Oct 20 '22 15:10

ouah


EDIT: please note that the body of this answer is made irrelevant by the comments it prompted

The theory of the other answers is fine, but probably doesn't help you. The actual problem is that you wrote:

uint8_t* point = ((uint8_t*)array)+1;

When you should have written something such as

uint8_t* point = (uint8_t*)(array+1);

because you need to increment the pointer as a pointer to an appropriate type (so that the increment operation will add the size of an element), before you cast it to something else.

But one might ask if you really intend to have a byte pointer to a 32-bit value. Perhaps you do intend to access it in bytewise fashion (beware that the byte ordering will vary between systems!). Or perhaps you really intended for point to be a pointer to a 32-bit value which is in turn a pointer to an 8-bit value somewhere else...

like image 2
Chris Stratton Avatar answered Oct 20 '22 14:10

Chris Stratton