把以下4个前提搞清楚以后,学习和使用汇编才有意义:
--------------------------------------------------------------------------------------
前提1:寄存器说明: EAX,EBX,EDX,ECX 通用寄存器,由程序员自己指定用途,也有一些不成文的用法: EAX:常用于运算。 EBX:常用于地址索引。 ECX:常用于计数。 EDX:常用于数据传递。 EIP 指令寄存器,指出当前指令所在的地址。 ESP 栈指针,指向当前线程的栈顶(问题:是不是每个函数都有自己的栈,那每个线程都有一个栈是什么意思呢)。 EBP 栈基址指针,对调试起着很重要的作用(Base)。 EDI,ESI 没有规定作什么用,一般用在源指针和目标指针的操作
前提2:Register-Delphi默认模式 参数传递模式...前三个数据.eax,edx,ecx...超过三个参数部分.放在堆栈传递 头三个不大于4个字节(DWORD)的参数从左到右的传入EAX,EDX,ECX寄存器;接下去的参数按从左到右压栈。函数自己恢复堆栈。 浮点数总压栈,不管它所占的字节是多少。 对象方法总是有一个Self隐含参数,这个参数在所有的参数前面,即总是传给EAX(所以对于对象方法,程序员看到的第一个参数在EDX里)。
前提3:EBP基址指针的作用 EBP是基址指针寄存器:一般用来确认堆栈帧的起始位置,也就是指向栈底。也就是说,一般一个函数入口的地址也就存放在EBP中(所以一般在进入函数的时候将ebp寄存器内容压栈,即保存其函数的上级调用函数的栈基地址,以便于以后返回调用)。
前提4:查看汇编代码 可以使用Ctrl+Alt+C打开Cpu View的Cpu调试窗口看里面的汇编代码。
--------------------------------------------------------------------------------------
1. 对栈的研究 栈是向下增长的,即每压一次栈,栈顶的地址就减少一次,也可以说ESP的值就减小一次。 栈是线程相关的,每一个线程都拥有一个栈。 程序利用ESP可以很灵活地访问栈,不一定要执行PUSH和POP栈顶才会改变,直接操作ESP也可以改变栈顶,也就是说ESP决定了栈顶的值。栈是有最大值的,通过编程环境可以设置,超出最大值就会发生栈溢出。
看一个简单的例子,下面的指令是一条压栈指令,意思是将EAX的值压入栈中: PUSH EAX 根据上面的性质,这条指令等价于下面的指令: SUB ESP, 4 MOV ESP, EAX Cliff:所谓的向下生长,就是下面的地址小(比如0x00000000),上面的地址大(比如FFFFFFFF)。分配栈的起始地址任意,但压栈的话,地址向下移动,地址值变小(就是在海平面压一块海绵下去,放一块海绵,地址就下降一些)。但是对于单个栈而言,栈就1M或者2M大小,在这个区间内怎么折腾都行,但不能任意扩大。但整个程序的运行,内存分配不见得是向下生长的,这里仅仅是栈。其实很简单,使用Delphi任意执行一条push语句,观察一下ESP值的变化就行了。
2. 函数如何被调用 其实很简单,就是一个跳转指令JMP,跳到函数的首地址去,并从那里开始执行指令。 比如下面的代码: C := Add(10, 20); 按照上面的讨论,汇编代码应该如下: MOV EAX, 10 MOV EDX, 20 JMP @Add 又遇到另一个问题:函数执行完后如何返回?为了解决这个问题,必须把 C := Add(10, 20)之后的指令地址保存起来。 MOV EAX, 10 MOV EDX, 20 PUSH [EIP + Len] JMP @Add 大概有人觉得函数调用实在是很常用的事情,于是干脆把最后两条指令合成一条,变成了Call,所以最后的汇编代码如下: MOV EAX, 10 MOV EDX, 20 CALL @Add 函数执行完后怎么在栈中找到返回地址?解决这个问题的关键点就是栈平衡。 假设它的代码是这样: Function Add(a, b: Integer): Integer; begin Result := a + b; end; 那么汇编代码就是这样: ADD EAX, EDX POP EDX // 问题:为什么是EDX?估计随便放的,执行完上一句Add指令以后EDX是闲置的,可以随便使用。执行函数前,把前一个函数的栈顶地址压栈了,现在只需简单出栈即可,出栈到任意一个寄存器以供跳转即可。跳转之后,这个寄存器存储的值也不再需要了。 JMP EDX 同样后两个指令太常用了,因此合成一条,成了Ret,最后的汇编代码是这样的: ADD EAX, EDX RET 从汇编角度函数调用大概就是如此。
结论:
① 未优化的Pascal代码与优化的汇编代码效率相差为45倍。 ② 优化的Pascal代码与优化的汇编代码效率相差为20倍。 ③ 优化的Pascal代码与未优化的汇编代码效率相差为2.9位。 ④ 未优化的汇编代码与优化的汇编效率相差为7倍。
参考: http://blog.csdn.net/suiyunonghen/article/details/2501737 http://blog.csdn.net/suiyunonghen/article/details/1909904 http://blog.csdn.net/suiyunonghen/article/details/1897142 http://blog.csdn.net/suiyunonghen/article/details/1897062
BASM for Beginners http://dennishomepage.gugs-cats.dk/BASM-filer/BASMForBeginners1.htm
--------------------------------------------------------------------------------------
汇编中通用寄存器的目的
1、EAX和AX:累加器,所有的I/O指令用它来与外部设备传送信息 2、EBX和BX:在计算存储单元地址时常用作基地址寄存器 3、ECX和CX:保存计数值 4、EDX和DX:做四字或二字运算时,可以把EDX(DX)和EAX(AX)组合在一起存放一个四字或二字长的数据,在对某些I/O操作时,DX可以放I/O的端口地址 5、ESP和SP:堆栈栈顶指针。 6、EBP和BP:基址寄存器 7、ESI和SI:源变址 8、EDI和DI:目的变址
返回值(对于intel C++, Visual C++, GCC而言): 1. 如果是地址或者整数就放在eax中 2. 如果字节类型就放在al中 3. 如果是浮点数类型,就放在浮点数堆栈的栈顶
浮点数常用指令(float占用4个字节,double占用8个字节): fld 压栈 fst 弹栈 fcom 比较 fnstsw 将协处理器的标志寄存器的内容拷贝到通用寄存器 fadd 加法 fdiv 除法 fmul 乘法 fsub 减法 例如: float a=5.89+8.90 fld 40BC7AE1h ; 压栈 5.89 fadd 410E6666h; 加上 8.90 fst [esp+var_4]; 将浮点堆栈顶部的值弹到变量中 浮点堆栈一共占有8个单元,每个单元占8个字节,栈顶为st(0)或称st,栈底为st(7) fld float对应的机器码是 0xD9 fld double对应的机器码是 0xDD fld long double对于的机器码是 0xDB
除法运算比乘法慢10倍(不做优化的情况下),所以使用公式来优化: a/b = (2^n/b)*(a/2^n)
VC++的Release版默认优化级别是O2
|
请发表评论