1)用VS2010新建Win32 Console Application,工程名为ACECore,工程建立完成后得到打开文件ACECore.cpp,代码如下:
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}
2)用VS2010查看汇编代码的方法:
1. VC必须处于debug状态才能看到汇编指令窗口。因此在上面代码return 0一句上设置断点。
2.按下F5键调试程序,当程序停在断点处时,打开菜单“Debug”下的“Windows”子菜单,选择“Disassembly”。这样就出现反汇编窗口,显示汇编代码:
--- g:/acecore/acecore/acecore.cpp ---------------------------------------------
// ACECore.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
int _tmain(int argc, _TCHAR* argv[])
{
00411350 push ebp
00411351 mov ebp,esp
00411353 sub esp,0C0h
00411359 push ebx
0041135A push esi
0041135B push edi
0041135C lea edi,[ebp-0C0h]
00411362 mov ecx,30h
00411367 mov eax,0CCCCCCCCh
0041136C rep stos dword ptr es:[edi]
return 0;
0041136E xor eax,eax
}
3)相关汇编指令:
push:把一个32位的操作数压入堆栈中,这个操作导致esp被减4。esp被称为栈顶,压入堆栈的数据越多,这个堆栈也就越堆越高,esp地址就越来越小。在32位平台上,esp每次减少4(字节)。
pop:esp被加4,一个数据出栈。pop的参数一般是一个寄存器,栈顶数据被弹出到这个寄存器中。
某些指令会“自动”地操作堆栈:call指令会把它的下一条指令的地址压入堆栈中,然后跳转到它调用的函数的开头处;而单纯的jmp是不会这样做的。同时,ret会自动弹出返回地址。
call的本质相当于push+jmp,ret的本质相当于pop+jmp。
不但push、pop、call和ret会操作堆栈,sub和add也可以用于操作堆栈。如果要一次在堆栈中分配4个4字节长整型的空间,那么没有必要4次调用push,很简单地把esp减去4*4=16即可。当然,也可以同样地用add指令来恢复它。
lea:取得地址(第二个参数)后放入到前面的寄存器(第一个参数)中。实际上,有时候lea用来做和mov同样的事情,比如赋值:
lea edi,[ebp – 0cch]
其中,方括弧表示存储器,也就是ebp-0cch这个地址所指的存储器内容。但是lea语法要求取[ebp-0cch]的地址,这个地址就是ebp-0cch,把这个地址放到edi中,也就是说,这等同于:
mov edi,ebp-0cch
但以上的mov指令时错误的,因为mov不支持后一个操作数写成寄存器减去数字。而lea支持,因此可以用lea来代替它。
Stos指令:
mov ecx, 30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
stos是串存储指令,它的功能是将eax中的数据放入edi所指的地址中,同时,edi会增加4(字节数)。rep时指令重复执行ecx中填写的次数。方括弧表示存储器,这个地址实际上就是edi的内容所指向的地址。这里的stos其实对应的是stosd,其他还有stosb、stosw,分别对应于处理4、1、2个字节。
上面代码中对堆栈30h*4(=0c0h)个字节初始化为0CCh(也就是int3指令的机器码),这样发生意外时执行堆栈里面的内容会引发调试中断。
==============lost的分割线===============
C函数的参数传递过程:
1)C语言程序通过堆栈把参数从函数外部传入到函数内部,同时,在堆栈中划分区域来容纳函数的内部变量。对于C语言默认的调用方式,函数调用方把参数反序(从右到左)地压入堆栈中,被调用方把堆栈复原。这些参数对齐到机器字长,16位、32位、64位CPU下分别对齐到2、4、8个字节。
2)函数调用规则指的是调用者和被调用者函数间传递参数及返回参数的方法,在Windows上,常用的有Pascal方式、WINAPI方式(_stdcall)、C方式(_cdecl)。
_cdecl调用规则:
(1)参数从右到左进入堆栈;
(2)在函数返回后,调用者要负责清除堆栈,所以这个调用常会生成较大的可执行文件。
_stdcall又称为WINAPI,其调用规则:
(1)参数从右到左入栈;
(2)被调用的函数在返回前自行清理堆栈,所以生成的代码比cdecl下。
Pascal调用规则:
(1)参数从左到右入栈;
(2)被调用参数在返回前自行清理堆栈;
(3)不支持可变参数的函数调用。
此外,在Windows内核中还常见有快速调用方式(_fastcall);在C++编译的代码中有this call方式(_thiscall)。
3)以如下函数作为例子分析:
void ACEFunction(int a, int b)
{
int c = a + b;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a = 1;
int b = 2;
ACEFunction(a, b);
return 0;
}
标准的C函数调用方式(_cdecl):
(1)调用者把参数反序地压入堆栈中;
(2)调用函数;
(3)调用者负责把堆栈清理复原。
注意:在Windows中,不管哪种调用方式都是返回值放在eax中,然后返回。外部从eax中得到返回值。