GP2X Open2x Libc - Possible Memset Problem.


ledow

Member
Joined
Jan 6, 2008
Messages
430
Age
44
Location
UK
Website
www.ledow.org.uk
I've just discovered (well, uncovered might be a more accurate term) a little bug in the memset function for the version of libc shipped with the Open2x toolchain. I have to say that I just "hit" this bug, happened to notice that it was a problem with memset and somebody else did all the clever tracing back to find its cause. Thanks to Simon Tatham (of PuTTY fame) for that. I don't know if it's a particular bug in this version of libc, or if it the libc was compiled wrong, or if it's a bug with the ARM-patches to this libc. However, somebody else may hit it so it's in everyone's interest that I share the problem.

I'll let a quote from Simon's email explain the problem, which occurs if you try to do:

CODE
memset(a pointer, a negative value < -1, a size).


QUOTE
In fact, the problem turned out to be in memset itself, which was in libc.so.6 in the arm-open2x-linux-apps-gcc-4.1.1-glibc-2.3.6_i686_linux.tar.bz2.zip archive you linked....
I think this is in fact a failure of standards compliance in that libc's memset. The C standard says that memset(s,c,n) fills the
memory with the value of c _converted_ to an unsigned char. That is, memset must behave as if it clips the value to fit in an unsigned char before doing anything. The memset in that libc does not.

What's actually happening is that memset is taking the input c value and doing the equivalent of

c |= c << 8;
c |= c << 16;

to get a 32-bit word containing four copies of c. Then it fills memory with that, a word at a time, and cleans up a few bytes at
each end separately. But, of course, if c has the top 24 bits set on input, then those bitwise OR operations don't make any difference, and memory will be filled with a 32-bit word in which three of the bytes are 0xFF no matter what the other one was. So memset ought to be doing

c &= 0xFF;

before it starts; and it isn't.



He suggests a workaround of replacing:

CODE
memset(thing, -2, stuff);


with

CODE
memset(thing, (unsigned char)-2, stuff);


Or you can just replace the memset with an explicit loop that sets each element of your array to -2, for example. Note that most libc versions don't require the explicit cast at all and just function normally (so your PC won't hit this bug even if it compiles the same code). It's only when you link that same code using that version of libc in the Open2x toolchain that you get problems. Rather than:

CODE
-2 -2 -2 -2 -2


filling the "thing", you get:

CODE
-1 -2 -2 -2 -1 -2 -2 -2 -1


or similar.

Hats off to Simon for doing the majority of the legwork (including downloading the Open2X toolchain, reading through pages of ARM assembly and eventually discovering the actual cause of the problem), and for mentioning something completely different that made me find the bug in the first place.
 
Also of note.. I wonder if the backend code for memset is being used implicitly by anything else, or by the compiler itself. (Likely would've been found before if so, but still.)

I almost exclusively use memset for setting to zero, or positive values; interesting find but I suppose its a bug thats lasted so long since so few people mass set to -ve values?

jeff
 
skeezix said:
I almost exclusively use memset for setting to zero, or positive values; interesting find but I suppose its a bug thats lasted so long since so few people mass set to -ve values?
As do I, which is why I had a lot of work to "stumble" across this bug. The source code for "Simon Tatham's Portable Puzzle Collection" (notably the "mines" game) is possibly the only place I've ever seen initialisation to negative values with memset. I have to say, though, that it "just works" everywhere you try it, except for the Open2X toolchain. The evidence for this is that STPPC has been ported to Windows, Linux, MacOS, Solaris, Palm, Nintendo DS, even a Java virtual "C" machine, all sorts, without anyone hitting the bug.

Plus, it's easy to overlook, especially if you use a variable as the "fill" integer - if the variable accidentally hits a negative number one time but not another, it would do weird stuff that would be very hard to track down. And even when it's "memset(pointer, -2, size)" it doesn't ring alarm bells because your brain just tends to "ignore" memset because we know what it does in simple terms (i.e. fill memory with -2 - what's wrong with that?).

The specs say that the value is "treated an unsigned int" but if you write something that represents -1 to RAM and then treat that RAM as signed int's, it should still give you -1! At least it should give you a uniform pattern throughout RAM, where this bug gives you "stripey" RAM - three values followed by a different one every fourth value.

At first, I just assumed that it was a total misuse of memset but several things tell me it's "standard" usage - firstly, the specs allow you to do it (even if they don't condone it) and secondly (and most importantly) you can do it in the vast majority of compilers and platforms without problem - and this particular instance appears to be a genuine bug in the libc rather than just an architecture quirk.

The ease of hitting the bug, the difficulty of "spotting" it, the logic of what's happening, the seriously unexpected results and the fact that the majority of compilers (including the same compiler on other platforms etc.) won't hit the bug are why I thought it needed a little announcement. The bloody thing annoyed the hell out of me for six months before I found it (admittedly, I wasn't expecting the bug to be there at all) and I'd rather people were aware, even if it gets fixed in the very next version of Open2x - a lot of people are going to be using the current dev tools and maybe even mis-using them for Pandora etc.
 
Last edited by a moderator:
I always compile with my own "library" that contains all the ARM assembly versions of the mem* functions, for added speed. Not that I'd probably have done this for STPPC but it would quickly fix the problem.
 
Back
Top