This is very easy to reproduce, just compile the sample code below (C or
C++, it doesn't matter) with /O2 or /Ox option.
Just adding a #pragma function(strcat) or compiling with /O1 or /Od removes
the bug.
I didn't try other version of Visual C++ than the one from Visual Studio.NET
2003 Entreprise Edition (french version, from MDSN subscription).
Conditions to make this bug happen:
1) an inlined strcat (intrisic) and
2) first parameter of the inlined strcat is a not-inlinable function call
returning the destination string pointer and
3) second parameter of the inlined strcat isn't a literal constant string
(the inlined strcat must have to do an inlined strlen on it, the optimizer
musn't be able to find a constant length).
// BEGINNING OF SAMPLE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <string.h>
// making the variables below global prevent optimizations that "hide" the
bug
char sz1[256] = "abc";
char sz2[] = ".ghi";
int main (int argc, char **argv)
{
// lstrcat (below) instead of a second strcat to prevent its inlinning,
because
// the lstrcat replaces a more complex function from a DLL of mine.
// The code generated for the inlined strcat is realy very bad !
strcat(lstrcat(sz1, "def"), sz2); // sz1 is (far) large enough for that
// should print "abcdef.ghi" but the lstrcat make an exception !
printf("%s\n", sz1);
return 0;
}
// END OF SAMPLE
The generated code (with my comments) is plainly wrong:
_main PROC NEAR
; Line 15
; THE INSTRUCTION BELOW APPEAR TOO SOON
push OFFSET FLAT:??_C@_03BHEEIFFN@def?$AA@ ; push second
parameter of lstrcat ("def")
mov eax, OFFSET FLAT:_sz2 ; get second parameter of strcat
; THE INSTRUCTION BELOW APPEAR TOO SOON TOO
push OFFSET FLAT:_sz1 ; push first parameter of lstrcat (sz1)
; inlined strlen(sz2)
mov edx, eax
$L20160:
mov cl, BYTE PTR [eax]
inc eax
test cl, cl
jne SHORT $L20160
; OOPS, WRONG ! backup ebx/esi AFTER pushing parameters for lstrcat
and before
; calling lstrcat !
push ebx
push esi
sub eax, edx ; end of the inlined strlen(sz2)
; STILL WRONG ! same reason as above
push edi
;THIS (WAS ABOVE) SHOULD BE HERE: push OFFSET
FLAT:??_C@_03BHEEIFFN@def?$AA@
mov esi, edx ; backup strlen(sz2) value
;THIS (WAS ABOVE) SHOULD BE HERE: push OFFSET FLAT:_sz1
mov ebx, eax ; backup sz2 pointer
; lstrcat below is COMPLETELY LOST with the random values pushed
from edi and esi !
; => exception
call DWORD PTR __imp__lstrcatA@8
; inlined strcat, skip existing chars in the (never) returned
pointer to sz1 from lstrcat
dec eax
$L20161:
mov cl, BYTE PTR [eax+1]
inc eax
test cl, cl
jne SHORT $L20161
; and copy sz2 at the end of sz1
mov ecx, ebx
shr ecx, 2
mov edi, eax
rep movsd
mov ecx, ebx
and ecx, 3
; Line 17
push OFFSET FLAT:_sz1
push OFFSET FLAT:??_C@_03OFAPEBGM@?$CFs?6?$AA@
rep movsb
call _printf
add esp, 8
pop edi
pop esi
; Line 18
xor eax, eax
pop ebx
; Line 19
ret 0
_main ENDP