微软C/C++编译器cl.exe
From: xuyibo.net Date: 2007-10-31 08:52 AM
【一点推证】
1. cl并没有显式调用ML,所以cl模块中必定含有自己的汇编模块。而且功能应该比ML要强大。毕竟ML对外发布的版本已经很久没有更新了。
2. 嵌入到VC中的/FA功能好像有点限制,就是在Release版本下无法生成.ASM文件(这又是微软加的一个可恶的限制,当然你完全可以用命令行实现反汇编输出代码)。这一点好像有点令人失望,但在CONSOLE下仍然可以生成汇编代码,而且生成的质量非常高,几乎跟IDA反汇编代码是对应的。
/FA Assembly-Only Listing Assembly code; .ASM
/FAc Assembly With Machine Code Machine and assembly code; .COD
/FAs Assembly With Source Code Source and assembly code; .ASM
/FAcs Assembly, Machine Code, and Source Machine, source, and assembly code; .COD
/Fafilename Listing File Name Use /Fa to specify a directory and/or filename for the selected type of listing file. By default, the base name of the listing file is the base name of the source file.
3.子函数堆栈排列
?pp@@YAHH@Z PROC NEAR ; pp
; File c.cpp
; {
push ebp
mov ebp, esp
; i = i++;
mov eax, DWORD PTR _i$[ebp]
mov DWORD PTR _i$[ebp], eax
mov ecx, DWORD PTR _i$[ebp]
add ecx, 1
mov DWORD PTR _i$[ebp], ecx
; return i;
mov eax, DWORD PTR _i$[ebp]
; }
pop ebp
ret 0
PUBLIC ?pp@@YAHH@Z ; pp
_TEXT SEGMENT
_i$ = 8
_i1$ = -4
_i2$ = -8
_i3$ = -12
?pp@@YAHH@Z PROC NEAR ; pp
; 4 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 5 : int i1 =2;
mov DWORD PTR _i1$[ebp], 2
; 6 : int i2 = 3;
mov DWORD PTR _i2$[ebp], 3
; 7 : i = i++;
mov eax, DWORD PTR _i$[ebp]
mov DWORD PTR _i$[ebp], eax
mov ecx, DWORD PTR _i$[ebp]
add ecx, 1
mov DWORD PTR _i$[ebp], ecx
; 8 : int i3 = 4;
mov DWORD PTR _i3$[ebp], 4
; 9 : return i;
mov eax, DWORD PTR _i$[ebp]
; 10 : }
mov esp, ebp
pop ebp
ret 0
?pp@@YAHH@Z ENDP
通过上面的汇编代码,我们发现C++编译器的作者给我们做了大部分的工作。我们都知道在C语言中,函数中的局部变量必须首先声明或定义一下,否则就会出错;而在C++语言中却没有这个限制,究其原因为什么?答案就在上面的代码中:C++编译器可以确定函数中用到的局部变量的个数,进而确定它们的总大小xxx,然后就像C语言一样在PUSH EBP, MOV EBP, ESP之后添加SUB ESP, xxx。有时候在SUB ESP, xxx之后还将可能用到的CPU寄存器压人堆栈。
函数返回时,如果是__cdecl方式的调用,则为RET 0, ADD ESP yyy。其中yyy为传入的参数的总大小。
好让我们看看函数堆栈的排布:函数__stdcall int pp(int i, int j)
下面让我们深入的分析一下C/C++语言中函数的三种调用方式:__stdcall, __cdecl, __fastcall。
#include
int __stdcall pp(int i, int j, int k, int l, int f)
{
int i1 =2;
int i2 = 3;
i = i++ + j;
int i3 = 4;
return i;
}
void main()
{
int i;
i= pp(12, 13, 14, 15, 16);
int *j = &i;
int z = 3;
printf('%d', i);
}
__stdcall:
PUBLIC ?pp@@YGHHHHHH@Z ; pp
_TEXT SEGMENT
_i$ = 8
_j$ = 12
_k$ = 16
_t1$ = -4
_t2$ = -8
_t3$ = -12
?pp@@YGHHHHHH@Z PROC NEAR ; pp
; 4 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 5 : int t1 =2;
mov DWORD PTR _t1$[ebp], 2
; 6 : int t2 = 3;
mov DWORD PTR _t2$[ebp], 3
; 7 : i = j + k;
mov eax, DWORD PTR _j$[ebp]
add eax, DWORD PTR _k$[ebp]
mov DWORD PTR _i$[ebp], eax
; 8 : int t3 = 4;
mov DWORD PTR _t3$[ebp], 4
; 9 : return i;
mov eax, DWORD PTR _i$[ebp]
; 10 : }
mov esp, ebp
pop ebp
ret 20 ; 00000014H
?pp@@YGHHHHHH@Z ENDP ; pp
_TEXT ENDS
PUBLIC _main
EXTRN _printf:NEAR
_DATA SEGMENT
$SG590 DB '%d', 00H
_DATA ENDS
_TEXT SEGMENT
_i$ = -4
_j$ = -12
_z$ = -8
_main PROC NEAR
; 13 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 14 :
; 15 : int i;
; 16 : i= pp(12, 13, 14, 15, 16);
push 16 ; 00000010H
push 15 ; 0000000fH
push 14 ; 0000000eH
push 13 ; 0000000dH
push 12 ; 0000000cH
call ?pp@@YGHHHHHH@Z ; pp
mov DWORD PTR _i$[ebp], eax
; 17 : int *j = &i;
lea eax, DWORD PTR _i$[ebp]
mov DWORD PTR _j$[ebp], eax
; 18 : int z = 3;
mov DWORD PTR _z$[ebp], 3
; 19 : printf('%d', i);
mov ecx, DWORD PTR _i$[ebp]
push ecx
push OFFSET FLAT:$SG590
call _printf
add esp, 8
; 20 : }
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
__cdecl:
PUBLIC ?pp@@YAHHHHHH@Z ; pp
_TEXT SEGMENT
_i$ = 8
_j$ = 12
_k$ = 16
_t1$ = -4
_t2$ = -8
_t3$ = -12
?pp@@YAHHHHHH@Z PROC NEAR ; pp
; 4 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 5 : int t1 =2;
mov DWORD PTR _t1$[ebp], 2
; 6 : int t2 = 3;
mov DWORD PTR _t2$[ebp], 3
; 7 : i = j + k;
mov eax, DWORD PTR _j$[ebp]
add eax, DWORD PTR _k$[ebp]
mov DWORD PTR _i$[ebp], eax
; 8 : int t3 = 4;
mov DWORD PTR _t3$[ebp], 4
; 9 : return i;
mov eax, DWORD PTR _i$[ebp]
; 10 : }
mov esp, ebp
pop ebp
ret 0
?pp@@YAHHHHHH@Z ENDP ; pp
_TEXT ENDS
PUBLIC _main
EXTRN _printf:NEAR
_DATA SEGMENT
$SG590 DB '%d', 00H
_DATA ENDS
_TEXT SEGMENT
_i$ = -4
_j$ = -12
_z$ = -8
_main PROC NEAR
; 13 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 14 :
; 15 : int i;
; 16 : i= pp(12, 13, 14, 15, 16);
push 16 ; 00000010H
push 15 ; 0000000fH
push 14 ; 0000000eH
push 13 ; 0000000dH
push 12 ; 0000000cH
call ?pp@@YAHHHHHH@Z ; pp
add esp, 20 ; 00000014H
mov DWORD PTR _i$[ebp], eax
; 17 : int *j = &i;
lea eax, DWORD PTR _i$[ebp]
mov DWORD PTR _j$[ebp], eax
; 18 : int z = 3;
mov DWORD PTR _z$[ebp], 3
; 19 : printf('%d', i);
mov ecx, DWORD PTR _i$[ebp]
push ecx
push OFFSET FLAT:$SG590
call _printf
add esp, 8
; 20 : }
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
__fastcall:
PUBLIC ?pp@@YIHHHHHH@Z ; pp
_TEXT SEGMENT
_i$ = -16
_j$ = -20
_k$ = 8
_t1$ = -4
_t2$ = -8
_t3$ = -12
?pp@@YIHHHHHH@Z PROC NEAR ; pp
; 4 : {
push ebp
mov ebp, esp
sub esp, 20 ; 00000014H
mov DWORD PTR _j$[ebp], edx
mov DWORD PTR _i$[ebp], ecx
; 5 : int t1 =2;
mov DWORD PTR _t1$[ebp], 2
; 6 : int t2 = 3;
mov DWORD PTR _t2$[ebp], 3
; 7 : i = j + k;
mov eax, DWORD PTR _j$[ebp]
add eax, DWORD PTR _k$[ebp]
mov DWORD PTR _i$[ebp], eax
; 8 : int t3 = 4;
mov DWORD PTR _t3$[ebp], 4
; 9 : return i;
mov eax, DWORD PTR _i$[ebp]
; 10 : }
mov esp, ebp
pop ebp
ret 12 ; 0000000cH
?pp@@YIHHHHHH@Z ENDP ; pp
_TEXT ENDS
PUBLIC _main
EXTRN _printf:NEAR
_DATA SEGMENT
$SG590 DB '%d', 00H
_DATA ENDS
_TEXT SEGMENT
_i$ = -4
_j$ = -12
_z$ = -8
_main PROC NEAR
; 13 : {
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
; 14 :
; 15 : int i;
; 16 : i= pp(12, 13, 14, 15, 16);
push 16 ; 00000010H
push 15 ; 0000000fH
push 14 ; 0000000eH
mov edx, 13 ; 0000000dH
mov ecx, 12 ; 0000000cH
call ?pp@@YIHHHHHH@Z ; pp
mov DWORD PTR _i$[ebp], eax
; 17 : int *j = &i;
lea eax, DWORD PTR _i$[ebp]
mov DWORD PTR _j$[ebp], eax
; 18 : int z = 3;
mov DWORD PTR _z$[ebp], 3
; 19 : printf('%d', i);
mov ecx, DWORD PTR _i$[ebp]
push ecx
push OFFSET FLAT:$SG590
call _printf
add esp, 8
; 20 : }
mov esp, ebp
pop ebp
ret 0
如果在函数中分配一个char数组,那么编译器自动将其值增加到下一个4倍边界。Getchar()函数也会引起一个4Bytes的局部变量分配,为什么呢?为了保存函数执行时的临时变量。请看下面的程序代码:
void main()
{
int i, j;
i = 12;
j = (i < 1) ? (g_test*5) : i*12;}
}
PUBLIC _main
_TEXT SEGMENT
_i$ = -4
_j$ = -8
// DWORD PTR -12+[ebp], this is the temp value position.
_main PROC NEAR
{
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
i = 12;
mov DWORD PTR _i$[ebp], 12 ; 0000000cH
j = (i < 1) ? (g_test*5) : i*12;
cmp DWORD PTR _i$[ebp], 1
jge SHORT $L791
mov eax, DWORD PTR _g_test
imul eax, 5
mov DWORD PTR -12+[ebp], eax
jmp SHORT $L792
$L791:
mov ecx, DWORD PTR _i$[ebp]
imul ecx, 12 ; 0000000cH
mov DWORD PTR -12+[ebp], ecx
$L792:
mov edx, DWORD PTR -12+[ebp]
mov DWORD PTR _j$[ebp], edx
: }
mov esp, ebp
pop ebp
ret 0
_main ENDP
【理论】
C1.DLL 667KB, Microsoft Visual C Compiler Front End
C1XX.DLL 1179KB, Microsoft Visual C++ Compiler Front End – QFE Version
C2.DLL 721KB, Microsoft 32-Bit 80x86 Compiler Back End
CL.EXE 50KB, Microsoft 32-Bit C/C++ Compiler Driver
C1.DLL
Import Librarys:
MSVCRT.DLL
KERNEL32.DLL
USER32.DLL
MSPDB60.DLL
Export Functions:
_AbortCompilerPass@4
_InvokeCompilerPass@12
C1XX.DLL
Import Librarys:
MSVCRT.DLL
KERNEL32.DLL
USER32.DLL
MSPDB60.DLL
OLE32.DLL
OLEAUT32.DLL
Export Functions:
_AbortCompilerPass@4
_InvokeCompilerPass@12
C2.DLL
Import Librarys:
MSPDB60.DLL
USER32.DLL
MSVCRT.DLL
KERNEL32.DLL,
MSOBJ10.DLL
Export Functions:
_AbortCompilerPass@4
_InvokeCompilerPass@12
CL.EXE:
Import Librarys:
USER32.DLL
MSPDB60.DLL
MSVCRT.DLL
KERNEL32.DLL
VERSION.DLL