Learning the code generated by the Visual Studio C ++ compiler, part 1

Possible duplicate:
Why is such complex code emitted to divide a signed integer into two?

Background

I am just learning x86 asm, studying the binary code generated by the compiler.

Code compiled using the C ++ compiler in Visual Studio 2010 beta 2 .

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.21003.01 for 80x86 

C code (sandbox.c)

 int mainCRTStartup() { int x=5;int y=1024; while(x) { x--; y/=2; } return x+y; } 

Compile it using the Visual Studio command line

 cl /c /O2 /Oy- /MD sandbox.c link /NODEFAULTLIB /MANIFEST:NO /SUBSYSTEM:CONSOLE sandbox.obj 

Disasm sandbox.exe in OllyDgb

The following starts at the entry point.

 00401000 >/$ B9 05000000 MOV ECX,5 00401005 |. B8 00040000 MOV EAX,400 0040100A |. 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX] 00401010 |> 99 /CDQ 00401011 |. 2BC2 |SUB EAX,EDX 00401013 |. D1F8 |SAR EAX,1 00401015 |. 49 |DEC ECX 00401016 |.^75 F8 \JNZ SHORT sandbox.00401010 00401018 \. C3 RETN 

Study

 MOV ECX, 5 int x=5; MOV EAX, 400 int y=1024; LEA ... // no idea what LEA does here. seems like ebx=ebx. elaborate please. // in fact, NOPing it does nothing to the original procedure and the values. CQD // sign extends EAX into EDX:EAX, which here: edx = 0. no idea why. SUB EAX, EDX // eax=eax-edx, here: eax=eax-0. no idea, pretty redundant. SAR EAX,1 // okay, y/= 2 DEC ECX // okay, x--, sets the zero flag when reaches 0. JNZ ... // okay, jump back to CQD if the zero flag is not set. 

This part bothers me:

 0040100A |. 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX] 00401010 |> 99 /CDQ 00401011 |. 2BC2 |SUB EAX,EDX 

You can lock everything, and the EAX and ECX values ​​will remain unchanged at the end. So what is the meaning of these instructions?

+7
c assembly x86
Nov 01 '09 at 20:06
source share
4 answers

All this

 00401010 |> 99 /CDQ 00401011 |. 2BC2 |SUB EAX,EDX 00401013 |. D1F8 |SAR EAX,1 

denotes y /= 2 . You see, a standalone SAR would not perform signed integer division as compiler author suggested. The C ++ 98 standard recommends that a signed signed integer round the result to 0, while a SAR only round to negative infinity. (Rounding to negative infinity is allowed, the choice remains for implementation). To implement rounding to 0 for negative operands, the above trick is used. If you use an unsigned type instead of a signed one, then the compiler will only generate one shift instruction, since there will be no problem with negative division.

The trick is quite simple: with a negative extension of the y sign, the template 11111...1 in EDX will be placed, which is actually -1 in 2 representations of the addition. The following SUB will effectively add 1 to EAX if the original y value was negative. If the original y was positive (or 0), EDX will hold 0 after expanding the sign, and EAX will remain unchanged.

In other words, when you write y /= 2 with signed y , the compiler generates code that does something more like the following

 y = (y < 0 ? y + 1 : y) >> 1; 

or better

 y = (y + (y < 0)) >> 1; 

Note that the C ++ standard does not require the rounding result to be rounded to zero, so the compiler has the right to make only one shift, even for signed types. However, usually compilers follow rounding guidelines to zero (or offer the ability to control behavior).

PS I do not know exactly what the purpose of this LEA instruction is. This is really non-op. However, I suspect that it might just be a placeholder statement inserted into the code for further correction. If I remember correctly, the MS compiler has an option that forces you to enter placeholder instructions at the beginning and at the end of each function. In the future, this instruction may be overwritten by a pattern with the CALL or JMP command, which will execute the patch code. This particular LEA was chosen only because it creates a no-op placeholder statement of the correct length. Of course, it could be something completely different.

+11
Nov 01 '09 at 20:18
source share

lea ebx,[ebx] is just a NOP operation. Its purpose is to align the beginning of the cycle in memory, which will make it faster. As you can see here, the start of the cycle starts at address 0x00401010, which is divided by 16, thanks to this instruction.

The operations CDQ and SUB EAX,EDX guarantee that division rounds a negative number to zero - otherwise the SAR rounds it, giving incorrect results for negative numbers.

+5
Nov 01 '09 at 20:16
source share

The reason the compiler emits this is:

 LEA EBX,DWORD PTR DS:[EBX] 

instead of semantically equivalent:

 NOP NOP NOP NOP NOP NOP 

.. this is that the processor executes one 6-byte instruction faster than six 1-byte instructions. It's all.

+2
Nov 02 '09 at 4:13
source share

This does not answer the question, but is helpful advice. Instead of spoofing work with OllyDbg.exe, you can force Visual Studio to generate an asm file for you, which has an added bonus that it can put in the source code as comments. It doesn't really matter for your current small project, but as your project grows, you can spend a lot of time figuring out which build code corresponds to the source code.

From the command line, you need the / FA and / Fa ( MSDN ) options.

Here is part of the output for your sample code (I compiled the debug code, so .asm is longer, but you can do the same for your optimized code):

 _wmain PROC ; COMDAT ; 8 : { push ebp mov ebp, esp sub esp, 216 ; 000000d8H push ebx push esi push edi lea edi, DWORD PTR [ebp-216] mov ecx, 54 ; 00000036H mov eax, -858993460 ; ccccccccH rep stosd ; 9 : int x=5; int y=1024; mov DWORD PTR _x$[ebp], 5 mov DWORD PTR _y$[ebp], 1024 ; 00000400H $LN2@wmain: ; 10 : while(x) { x--; y/=2; } cmp DWORD PTR _x$[ebp], 0 je SHORT $LN1@wmain mov eax, DWORD PTR _x$[ebp] sub eax, 1 mov DWORD PTR _x$[ebp], eax mov eax, DWORD PTR _y$[ebp] cdq sub eax, edx sar eax, 1 mov DWORD PTR _y$[ebp], eax jmp SHORT $LN2@wmain $LN1@wmain: ; 11 : return x+y; mov eax, DWORD PTR _x$[ebp] add eax, DWORD PTR _y$[ebp] ; 12 : } pop edi pop esi pop ebx mov esp, ebp pop ebp ret 0 _wmain ENDP 

Hope this helps!

+1
Nov 01 '09 at 21:06
source share



All Articles