汇编指令
- ret n : 在ret指令后给栈指针加上操作数
- test eax,eax 如果eax的值为0,那么逻辑与的运算结果为0,设置ZF为1,否则ZF为0
- lea :加载有效地址
- sar : 有符号数右位移指令,这个是算术右位移,补全时用的最高位
- shr : 无符号数右位移指令,这个是逻辑右位移,补全时用0
- shl : 左位移指令,操作数的最高位进入标志位CF,最低位补零
- cdq :这个一般就是用在除法之前,然后作用就是把32位数字扩展成为64位数字,方法就是把eax的最高位全复制到edx中的每一位,进而形成一个64位数据
数据结构
局部变量
- 利用栈存放局部变量, 寻找变量:[ebp-xxx] ,参数调用: [ebp+xxx] ,编译器也可以用 “push reg” 取代指令 “sub esp ,4” 来节省几个字节的空间,”pop ecx”之类的指令也有压缩栈空间的作用,另外局部变量的起始值是随机的,是其他函数执行后留在栈中的垃圾数据。
- 还可以利用寄存器来存放局部变量,除了栈占用的2个寄存器,编译器会利用剩下的6个通用寄存器尽可能有效的存放局部变量。
全局变量
- 全局变量通常位于.data段,程序访问全局变量一般会用一个固定的硬编码的地址进行寻址,如果放到了只读段则说明它是一个常量。
结构体和数组
- 其寻址方式一般是[基地址+n] 例如:mov eax, [407030h(基地址) + eax(偏移)]
虚函数
- 待学
控制语句
- switch case : 跟if else 语句类似,但是如果case 的取值表示一个算术级数,那么编译器会利用一个跳转表来实现。此时jmp dword ptr [4*eax + 004010b0] 指令相当于switch(a) ,根据eax的值进行索引
转移指令
- 短转移:有条件和无条件跳转机器码均为2字节,转移的范围是-128-127字节
- 长转移:有条件转移6字节,两字节表示转移类型(je,jg,jns),四字节表示转移偏移量。无条件转移5字节,因为jmp只需要一个字节,四字节表示偏移即可
- 子程序调用(call) : 一类是平常的,类似长转移,一种是调用的参数涉及寄存器,栈等值。例如:”call dword ptr [eax+2]”
- 位移量 = 目的地址 - 起始地址 - 跳转指令本身的长度
数学运算符
- 整数加减法:
一般情况下就是add sub这些,但是因为编译优化的存在,这些明显的指令会被替换,lea指令允许用户在一个时钟内完成c = a + b + 78h的计算,其中a b c都是在有寄存器的情况下有效,会编译成lea c,[a+b+78h],这种技巧可以使多个变量的求和在一个指令周期内完成,同时可以通过任何寄存器返回结果。例如: lea eax,[eax+ecx+78h] (78h不是固定的,只是表明可以是立即数) - 整数乘法
这一般就是mul和imul了,但是优化会进行改变,如果乘2的次幂较大可能用shl左移指令,同时也可能会用lea指令,类似于上面的加法。 - 整数的除法
这个一般是div,idiv,除法代价高,大概需要比乘法运算多消耗十倍的CPU时钟,这个的替换类似于乘法,也是当除数是2的幂 的话,无符号尽量用shr,有符号尽量用sar,同时优化的时候尽量会把除法替换成乘法一般就是倒数相乘a/b=a*(1/b)
文本字符串
- 字符串大小写转换:
大写字母 41h - 5Ah 小写字母 61h - 7Ah 它们之间的转换方式就是原来ASCII的值+/- 20h
还有一种大小写转化方法是基于位操作,我们可以发现二进制表示的时候大写字母A的第五位是0,而小写a的第五位是1,其他可以类推,所以就有以下方式Main proc near lea bx, title+! mov cx,31 B20: mov ah,[bx] cmp ah,61h jb B30 cmp ah,7Ah ja B30 and ah ,1101 1111b ;and 将ah的第五位指令置0(11011111b = DFh) mov [bx],ah B30: inc bx loop B20 ret Main endp