| Summary: | SDL macro on x86 violates GCC and SYS V ABI assumptions | ||
|---|---|---|---|
| Product: | SDL | Reporter: | Christoph Mallon <christoph.mallon> |
| Component: | video | Assignee: | Ryan C. Gordon <icculus> |
| Status: | RESOLVED FIXED | QA Contact: | Sam Lantinga <slouken> |
| Severity: | normal | ||
| Priority: | P2 | ||
| Version: | HG 1.2 | ||
| Hardware: | x86 | ||
| OS: | All | ||
>Fixed in svn revision #3535 for the 1.2 branch, and #3536 for the 1.3 >branch, thanks! > >--ryan. the bug was fixed in SVN. Obviously the bug owner don't seem to check his bugzilla account frequently... |
On x86 when bliting a software surface onto itself SDL_BlitSurface() internally sets the cpu direction flag, which causes following memcpy()s (or similar) to fail. The x86 inline assembler version of the SDL macro SDL_revcpy() sets the direction flag ("std"), but does not clear it ("cld"). This is invalid according to GCC (inline assembler, which sets the direction flag, must reset it[0]) and the SYS V ABI (functions must leave with the direction flag cleared[1]). The macro is (indirectly, exact call sequence below) used in SDL_BlitSurface(), so this call sometimes returns with the direction flag set. This happens for bliting a software surface onto itself with the destination coordinates set right/down of the source coordinates (typical use of this is scrolling left/up). Later on other parts of the code (like inlined memcpy()) cause memory corruption. Call sequence: SDL_BlitSurface() (#define SDL_Blit_Surface SDL_BlitUpper) SDL_BlitUpper() SDL_BlitLower() src->map->sw_blit() (function pointer to SDL_SoftBlit()) SDL_SoftBlit() src->map->sw_data->blit() (function pointer to SDL_BlitCopyOverlap()) SDL_BlitCopyOverlap() SDL_revcpy() Here's a sample program, which demonstrates the problem. After the call to SDL_BlitSurface() the direction flag will be set, which is tested and an error is printed: #include <SDL.h> #include <stdio.h> #include <stdlib.h> static inline int TestDirectionFlag(void) { unsigned eflags; __asm__ __volatile__("pushf\n\tpop %0" : "=r" (eflags)); return (eflags & 0x400) != 0; } int main(void) { SDL_Init(SDL_INIT_VIDEO); SDL_Surface* const s = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE); SDL_Rect src_rect = { 0, 0, 630, 470 }; SDL_Rect dst_rect = { 10, 10, 630, 470 }; if (TestDirectionFlag()) { fputs("direction flag set BEFORE SDL_BlitSurface()\n", stderr); abort(); } SDL_BlitSurface(s, &src_rect, s, &dst_rect); if (TestDirectionFlag()) { fputs("direction flag set AFTER SDL_BlitSurface()\n", stderr); abort(); } SDL_Quit(); return 0; } A Quick fix is to add a "cld" after the "rep movs" sequence in the SDL_revcpy() macro in SDL_stdinc.h: --- include/SDL_stdinc.h.orig 2006-06-27 06:48:32.000000000 +0200 +++ include/SDL_stdinc.h 2008-01-27 16:39:16.000000000 +0100 @@ -319,6 +319,7 @@ __asm__ __volatile__ ( \ "std\n\t" \ "rep ; movsl\n\t" \ + "cld\n\t" \ : "=&c" (u0), "=&D" (u1), "=&S" (u2) \ : "0" (n >> 2), \ "1" (dstp+(n-4)), "2" (srcp+(n-4)) \ A better fix is to replace the macro by a simple call to memmove() (the non-x86 version of this is very slow and even the x86 assembler magic could be done better), i.e.: #define SDL_revcpy(dst, src, len) memmove((dst), (src), (len)) [0] http://gcc.gnu.org/gcc-4.3/changes.html - "IA-32/x86-64", fourth bullet [1] http://www.sco.com/developers/devspecs/abi386-4.pdf - page 38 EFLAGS