Pointer Casting: Bug In Arm-gcc?


Omni

Still Fresh
Joined
Apr 15, 2007
Messages
2
i'm dealing with a weird problem. the following routine uncompresses some data. it runs fine on x86-systems:
CODE

char loop;
char indic;
unsigned char *readback;
int size;
unsigned short int temp;
int i;

do
{
loop = 8;
indic = *(source++); ///Read in one byte
do
{
if (!(indic & 1)) /// if bit is not set
{
temp = *((short*)source);
source += 2;
size = temp & 0x0F; ///size defines length of sequence
size += 2;///sequence length 0 & 1 don't make sense
decompressedSize -= size;
readback = (destination - (temp >> 4)) - 1; ///write back from output to output
for (i = 0; i < size; i++)
{
*(destination++) = *(readback++);
}
if (decompressedSize <= 0)
return;
loop--;
}
else
{
*(destination++) = *(source++);
loop--;
decompressedSize--;
if (decompressedSize <= 0)
return;
}
indic >>= 1;
}while (loop);
}while (decompressedSize); ///work through the whole buffer!


but compiled with arm-gcc, there seems to be a problem with the line
CODE

temp = *((short*)source);



the first few bytes of the compressed data are for example
03 00 20 1F 00 1F

the correct behaviour would be (may source start at pos 0):
transfer 1 byte from source to indic , increase source
=> source = 1, indic=3
=> transfer 1 byte from source to destination, increase source , shift indic
=> source = 2, indic=1
=> transfer 1 byte from source to destination, increase source, shift indic
=> source = 3, indic=0
=> indic = 0 => transfer 2 bytes from source to temp, increase source by 2
now temp should contain the value 0x001F (because it's little endian)

but instead, on the gp2x, temp contains the value 0x1F20.

if i use the following statement instead, it works correctly on gp2x:
CODE

temp = (*(source+1)<<8)+(*source);


is this a bug in the casting from char* to short* or was is pure luck, that this statement worked correctly on x86? i'm not familiar with the expected behaviour for casting a pointer, i just never expected that this could lead to a problem :(
 
im not sure if this can help you ... but as far as I know you cannot read from unaligned source on ARM processor. It was the same thing we i was working on GBA.

short -> align on 0x2;
long -> align on 0x4;

we can read the source as char and copy it in a temp short if you need it in short. Make sure to have it in the proper endian.

Hope it helps
Alain
 
QUOTE
=> source = 3, indic=0
=> indic = 0 => transfer 2 bytes from source to temp, increase source by 2
now temp should contain the value 0x001F (because it's little endian)


Yup, to access a short, you must be on an even address, on x86, it doesn't matter.

CP15 control register 1 bit 1 will allow you to disable data address alignment fault checking, but Linux may not like that ;) (and you can't change it unless your in SVC mode anyway, which means a kernel module).
 
I had this problem too. You can use memcpy() as a workaround.
 
There's a way to get GCC to insert the fixups for you, but I don't remember exactly what the incantation is. It's the same as doing temp = (*(source+1)<<8)+(*source); anyway, just automated.

It's generally bad form to read multibyte values directly out of a file by casting, since it's not portable across endiannesses. (and compilers if you go up to structs) Not so important when you have complete control over the data source and compiler...
 
thanks for your answers :)

i'm just wondering now: why didn't the program crash with an unhandled exception when the misaligned access occured?
 
I would have thought arm-gcc would have been smart enough to work around it and do a 4byte load from an aligned address. my decompression code has not given me any problems and I am doing byte load/stores at odd addresses without any problems.
 
from www.arm.com/pdfs/DAI0034A_efficient_c.pdf

QUOTE
Where possible, it is best to avoid using char and short as local variables. For the types char and short the compiler needs to reduce the size of the local variable to 8 or 16 bits after each assignment. This is called sign-extending for signed variables and zero-extending for unsigned variables. It is implemented by shifting the register left by 24 or 16 bits, followed by a signed or unsigned shift right by the same amount, taking two instructions (zero-extension of an unsigned char takes one instruction). These shifts can be avoided by using int and unsigned int for local variables. This is particularly important for calculations which first load data into local variables and then process the data inside the local variables. Even if data is input and output as 8- or 16-bit quantities, it is worth considering processing them as 32-bit quantities.


not sure if this is helpful or relevant, but it's a little more on why shorts are a bad idea i guess. if you haven't seen that doc, it's got some good stuff in there.
 
YakumoFuji said:
I would have thought arm-gcc would have been smart enough to work around it and do a 4byte load from an aligned address. my decompression code has not given me any problems and I am doing byte load/stores at odd addresses without any problems.
The compiler won't know whether the read will be aligned or unaligned at runtime.
 
Last edited by a moderator:
Dr_Ian said:
The compiler won't know whether the read will be aligned or unaligned at runtime.
considering how alignment specific arm is, it could be feasible to have a simple routine that says, hey, this is an odd offset... subtract 1 and read up a dword shift the data over a few bits and return..

its not that complex.

from the gcc manual
QUOTE
-malignment-traps
Generate code that will not trap if the MMU has alignment traps enabled. On ARM architectures prior to ARMv4, there were no instructions to access half-word objects stored in memory. However, when reading from memory a feature of the ARM architecture allows a word load to be used, even if the address is unaligned, and the processor core will rotate the data as it is being loaded. This option tells the compiler that such misaligned accesses will cause a MMU trap and that it should instead synthesise the access as a series of byte accesses. The compiler can still use word accesses to load half-word data if it knows that the address is aligned to a word boundary


linux 2.4 also has an misaligned trap that will grab and shift the bytes automatically.
 
Last edited by a moderator:
Yes but that will slow down most of your memory reads because the compiler can't know in a lot of cases whether it is aligned or not. Depends if you're going for the fatest code or the neatest or whatever. I like you.
 
Back
Top