3
votes

I'm trying to understand what this test does exactly. This toy code

int _tmain(int argc, _TCHAR* argv[])
{
    int i;
    printf("%d", i);
    return 0;
}

Compiles into this:

int _tmain(int argc, _TCHAR* argv[])

{ 012C2DF0 push ebp
012C2DF1 mov ebp,esp
012C2DF3 sub esp,0D8h
012C2DF9 push ebx
012C2DFA push esi
012C2DFB push edi
012C2DFC lea edi,[ebp-0D8h]
012C2E02 mov ecx,36h
012C2E07 mov eax,0CCCCCCCCh
012C2E0C rep stos dword ptr es:[edi]
012C2E0E mov byte ptr [ebp-0D1h],0

int i;
printf("%d", i);

012C2E15 cmp byte ptr [ebp-0D1h],0
012C2E1C jne wmain+3Bh (012C2E2Bh)
012C2E1E push 12C2E5Ch
012C2E23 call __RTC_UninitUse (012C10B9h)

012C2E28 add esp,4
012C2E2B mov esi,esp
012C2E2D mov eax,dword ptr [i]
012C2E30 push eax
012C2E31 push 12C5858h
012C2E36 call dword ptr ds:[12C9114h]
012C2E3C add esp,8
012C2E3F cmp esi,esp
012C2E41 call __RTC_CheckEsp (012C1140h)

return 0;

012C2E46 xor eax,eax
} 012C2E48 pop edi
012C2E49 pop esi
012C2E4A pop ebx
012C2E4B add esp,0D8h
012C2E51 cmp ebp,esp
012C2E53 call __RTC_CheckEsp (012C1140h)
012C2E58 mov esp,ebp
012C2E5A pop ebp
012C2E5B ret

The 5 lines emphasized are the only ones removed by properly initializing the variable i. The lines 'push 12C2E5Ch, call __RTC_UninitUse' call the function that display the error box, with a pointer to a string containing the variable name ("i") as an argument.

What I can't understand are the 3 lines that perform the actual test:

012C2E0E mov byte ptr [ebp-0D1h],0
012C2E15 cmp byte ptr [ebp-0D1h],0
012C2E1C jne wmain+3Bh (012C2E2Bh)

It would have seemed the compiler is probing the stack area of i (setting a byte to zero and immediately testing whether it's zero), just to be sure it isn't initialized somewhere it couldn't see during build. However, the probed address, ebp-0D1h, has little to do with the actual address of i.

Even worse, it seems if there were such an external (other thread?) initialization that did initialize the probed address but to zero, this test would still shout about the variable being uninitialized.

What's going on? Maybe the probe is meant for something entirely different, say to test if a certain byte is writable?

3
The code itself is zeroing the location it tests, so the branch should never be taken and thus the message printed. It doesn't make much sense to me, unless this was compiled without optimization.Jester
/RTC lives only in unoptimized builds.Ofek Shilon

3 Answers

4
votes

Here is my guess: the compiler probably allocates flags in memory showing the initialization status of variables. In your case for variable i this is a single byte at [ebp-0D1h]. The zeroing of this byte means i is not initialized. I assume if you initialize i this byte will be set to non-zero. Try something run-time like this: if (argc > 1) i = 1; This should generate code instead of omitting the whole check. You can also add another variable, and see if you get two different flags.

The zeroing of the flag and the testing just happen to be consecutive in this case, but that might not always be the case.

7
votes

[ebp-0D1h] is a temporary variable used by the compiler to track "initialized" status of variables. If we modify the source a bit, it will be more clear:

int _tmain(int argc, _TCHAR* argv[])
{
    int i, j;
    printf("%d %d", i, j);
    i = 1;
    printf("%d %d", i, j);
    j = 2;
    return 0;
}

Produces the following (irrelevant parts skipped):

mov DWORD PTR [ebp-12], -858993460      ; ccccccccH
mov DWORD PTR [ebp-8], -858993460       ; ccccccccH
mov DWORD PTR [ebp-4], -858993460       ; ccccccccH
mov BYTE PTR $T4694[ebp], 0
mov BYTE PTR $T4693[ebp], 0

In prolog, variables are filled with 0xCC, and two tracking variables (one for i and one for j) are set to 0.

; 7    :        printf("%d %d", i, j);    
    cmp BYTE PTR $T4693[ebp], 0
    jne SHORT $LN3@main
    push    OFFSET $LN4@main
    call    __RTC_UninitUse
    add esp, 4
$LN3@main:
    cmp BYTE PTR $T4694[ebp], 0
    jne SHORT $LN5@main
    push    OFFSET $LN6@main
    call    __RTC_UninitUse
    add esp, 4
$LN5@main:
    mov eax, DWORD PTR _j$[ebp]
    push    eax
    mov ecx, DWORD PTR _i$[ebp]
    push    ecx
    push    OFFSET $SG4678
    call    _printf
    add esp, 12                 ; 0000000cH

This corresponds roughly to:

if ( $T4693 == 0 )
  _RTC_UninitUse("j");
if ( $T4694 == 0 )
  _RTC_UninitUse("j");
printf("%d %d", i, j);

Next part:

; 8    :        i = 1;    
    mov BYTE PTR $T4694[ebp], 1
    mov DWORD PTR _i$[ebp], 1

So, once i is intialized, the tracking variable is set to 1.

; 10   :        j = 2;
mov BYTE PTR $T4693[ebp], 1
mov DWORD PTR _j$[ebp], 2

Here, the same is happening for j.

-1
votes
C7060F000055    mov     dword ptr [esi],5500000Fh
C746048BEC5151  mov     dword ptr [esi+0004],5151EC8Bh

b. And one of its later generations:

BF0F000055  mov     edi,5500000Fh
893E    mov     [esi],edi
5F  pop     edi
52  push    edx
B640    mov     dh,40
BA8BEC5151  mov     edx,5151EC8Bh
53  push    ebx
8BDA    mov     ebx,edx
895E04  mov     [esi+0004],ebx

c. And yet another generation with recalculated ("encrypted") "constant" data:

BB0F000055  mov     ebx,5500000Fh
891E    mov     [esi],ebx
5B  pop     ebx
51  push    ecx
B9CB00C05F  mov     ecx,5FC000CBh
81C1C0EB91F1    add     ecx,F191EBC0h ; ecx=5151EC8Bh