I spent a rather long time today trying to figure out a rather obscure bug. I few months ago I bought a gp2x, an open portable gaming system (and media player, etc.). I've really enjoyed it so far. The open platform for development has lent itself it many fun apps, console emulators the most conspicuous I think, but it's also fun to experiment and develop with it. I recently ported an asteroids clone I wrote and the whole process was actually fairly simple. As such, I decided to take on a larger task of porting something like FreeCNC, which as far as I know, no one has done yet.
After some simple trouble with library version conflicts, I got the program compiling correctly. However, if wasn't reading files quite right. I spent a little while investigating and discovered the problem was in a function which essentially hashes filenames. In the end, it was this snippet of code which was giving the trouble:
char buffer[13]; ... calc = ROL(calc)+*(long *)(buffer+i)
In particular, this is trying to read 4 bytes from this character array as an integer. At first I though the trouble might be endian-ness, but both the gp2x (an ARM processor) and x86 use big-endian*. I finally resorted to running gdb and sifting through assembly level instructions.
$r2 = 0xbffffd7c $r3 = 0x41434f4c 0x0000a310 <_ZN8MIXFiles7calc_idEPc+52>: str r3, [r2]
Here, the register $r2 points to buffer and $r3 is the value we're trying to save into the buffer. After this we would expect *0xbffffd7c = 0x41434f4c but instead see *0xbffffd7c = 0x08db9841. If I purposely set *0xbffffd7c = 0xffffffff and repeat this instruction we see *0xbffffd7c = 0xffffff41.
Clearly the problem lies in this single str instruction, and was resident to the ARM architecture. So of course it was time to go to the processor documentation. Reading the fine print finally reveals what I was looking for.
A word store (STR) should generate a word aligned address. The word presented to the data bus is not affected if the address is not word aligned. That is, bit 31 of the register being stored always appears on data bus output 31. Yep, since the buffer was an odd-sized array and stored on the stack, it started on an odd number in memory. Hence its address was not word aligned, and so as per the documentation, the store did not have the expected result. Here's a short program which shows what I'm talking about.
| x86 | ARM | buf=BFBF3471, buf%4=1 Num: 4C4F4341 Buf+0: 4C4F4341 Buf+1: 4C4F4341 Buf+2: 4C4F4341 Buf+3: 4C4F4341 | buf=BFFFFD75, buf%4=1 Num: 4C4F4341 Buf+0: 4F434100 Buf+1: 43410000 Buf+2: 41000000 Buf+3: 4C4F4341 |
Perhaps the lesson to learn here is that when casting pointers, it's buyer-beware, and there are safer bitwise manipulations to use, though certainly less efficient.
*Edit: Both x86 and the ARM processor in question are little-endian, not big-endian. Typo on my part, but notwithstanding the point is that they both had the same endian-ness, so it wasn't where the issue lay.
|