启动函数
源码里必须实现一个WinMain函数,先执行由编译器生成的相关代码,再调用WinMain函数。分析过程中可以略过启动函数。
函数的参数
函数传参有3种方式:栈方式、寄存器方式、全局变量隐含参数传递的方式。
- 栈方式:
一依次把参数压入栈,然后调用函数。函数结束后,由调用者或函数本身修改栈,恢复栈,叫作平衡栈数据。非优化编译器用一个专门的寄存器(通常是ebp)对参数进行寻址。在ret指令后加一个操作数,表示在ret指令后给栈指针esp加上操作数。如ret 8
相当于ret; add esp,8
- 利用寄存器传递参数:
这种方式没有标准,但大多数遵循Fastcall规范。 - 名称修饰约定:
为了允许操作符的重载,c++编译器往往会按某种规则改写每一个入口点的符号名,从而允许同一个名字有多个用法。称为名称改编或名称修饰。
函数的返回值
- 用return操作符返回值:
返回值在eax中。如果放不下,则高32位放在edx,低32位放在eax - 通过参数按引用方式返回值:
调用函数传的是地址。
数据结构
局部变量
- 放在栈中
- 放在寄存器中
全局变量
全局变量通常位于数据区块(.data)的一个固定地址处,要访问时,一般用固定的硬编码地址直接对内存进行寻址。静态变量和全局变量类似,都可以按直接方式寻址。区别是静态变量作用范围有限,仅在定义这些变量的函数内有效。
数组
数组地址通过基址加变址寻址实现的。
数学运算符
- 整数的加减法:
一般情况是add和sub。编译优化后,可能会用lea代替,lea速度很快。lea c,[a+b+78]
的意思就是c=a+b+78h
。 - 整数的乘法:
一般是mul、imul,编译优化后也可能是lea。shl左移代替乘2的幂。加法对于提高3、5、6、7、9等数的乘法效率很有用,如eax*5
可以写成lea eax,[eax*4+eax]
- 整数的除法:
一般是div、idiv。除法的运算代价很大。如果除数是2的幂,可以用shr代替。shr是和无符号运算,sar用来进行有符号运算。编译器优化后,会用乘法代替除法。最常用的优化公式就是倒数相乘。
文本字符串
- c字符串
也称ASCIIZ字符串,Z表示\0
,为结束标志,\0
代表ASCII为0的字符,是不可以显示的字符,是空操作符。 - DOS字符串
以$
作为终止字符,少见 - PASCAL字符
没有终止字符,但在字符串头部定义了1字节,用于指示当前字符串的长度,不能超过255个字符 - Delphi字符串
有双字节Delphi和四字节,就是开头表示长度的字节分别有2和4个。少见。