复习常用的汇编指令

 复习常用的汇编指令

   注:该部分参考《天书夜读:从汇编语言到windows内核编程》谭文,邵坚磊著的第一章部分。

1.1用VC 6.0查看汇编代码

      在VC 6.0下创建工程,并建立1.cpp源文件。如下:

       

      设置断点,打开菜单“Debug” 下的“Windows”子菜单,选择“Disassembly"。出现如下的反汇编代码:

        

下面讲解常用的汇编指令。

1.2常用汇编指令

  1.2.1堆栈相关指令

      push:把一个32位的操作数压入堆栈中。这个操作导致esp被减4。esp被形象地称为栈顶。我们认为顶部是

地址小的区域。那么,压入的堆栈的数据越多,这个堆栈也就越堆越高,esp也就越来越小。在32位平台上,esp每次减少4(字节)。

      pop:相反,esp被加4,一个数据出栈。pop的参数一般是一个寄存器,栈顶的数据被弹出到这个寄存器中。

      sub:减法。第一个参数是被减数所在的寄存器;第二个参数是减数。(对应的还有add指令。)
      add:加法。

      ret:返回。相当于跳转回调用函数的地方。(对应的call指令来调用函数,返回到call之后的下一条指令。)

      call:调用函数。

      call和jmp的不同之处:call指令会把它的下一条指令的地址,然后跳转到它调用的函数的开头处;而单纯的jmp是不会这样做的。同时,ret会自动地弹出返回地址。

      注:call的本质相当于push+jmp。ret的本质相当于pop+jmp。
      C语言函数的变量保存在栈里。

  1.2.2数据传送指令

      mov:数据移动。第一个参数是目的,第二个参数是来源。在C语言中相当于赋值号。
      xor:异或。这虽然是逻辑运算的指令,但是有趣的是,xor eax,eax这样的操作常用来代替mov eax,0。好处是速度更快,占用字节数更少。

      lea:取得地址(第二个参数)后放入到前面的寄存器(第一个参数)中。

      注:见到xor eax,eax,应该马上明白这是清零操作。

      但实际上,有时候lea用来做和mov同样的事情,比如赋值。看下面一条指令:
      lea edi,[ebp-0cch]
      方括弧表示存储器,也就是ebp-0cch这个地址所指的存储器的内容。但是lea要求取[ebp-0cch]的地址,
那么地址也就是ebp-0cch,这个地址将被放入edi中。换句话说,这等同于:
      mov edi,ebp-0cch
      但是以上mov指令是错误的,因为mov不支持后一个操作数写成寄存器减去数字,但是lea支持,所以可以用lea来代替它。

      为了讲解stos,下面解说前面提到的代码:
       

      stos是串存储指令,它的功能是将eax中的数据放入edi所指的地址中,同时edi会增加4(字节数)。rep使指令重复执行ecx中填写的次数。方括弧表示存储器,这个地址实际上就是edi的内容所指向的地址。这里stos其实对应的是stosd,其它还有stosb、stosw,分别对应于处理4、1、2个字节,这里对堆栈中30h*4(0c0h)个字节初始化为0cch(也就是int 3指令的机器码),这样发生意外时执行堆栈里面的内容会引发调试中断。

  1.2.3跳转与比较指令

     jmp:无条件跳转。
     jg:大于的时候跳转。
     jl:小于的时候跳转。
     jge:大于等于的时候跳转。
     cmp:比较。是jg、jl、jge之类的条件指令的执行条件。

1.3C函数的参数传递过程 

      函数和堆栈的关系密切,这是因为:我们通过堆栈把参数从外部传入到内部。此外,我们在堆栈中划分区域来容纳函数的内部变量。
      调用 push 和 pop 指令的时候,寄存器 esp 用于指向栈顶的位置——栈顶总是栈中地址最小的位置。push 执行的结果,esp 总是减少。pop 则增加。对于C程序默认的调用方式,堆栈总是调用方把参数反序(从右到左)地压入堆栈中,被调用方把堆栈复原。这些参数对齐到机器字长,16位、32位、64位CPU分别对齐到2、4、8个字节。这种调用是C编译器默认的C方式。

      

     C 语言所写的程序中,堆栈用于传递函数参数。这时称为调用栈。 写一个简单的函数如下: 

 

       
     这是标准的 C 函数调用方式.其过程是:
 

          1)  调用方把参数反序的压入堆栈中。
          2)  调用函数。
          3)  调用者把堆栈清理复原。
      这就是C编译器默认的_cdecl方式,而Windows API一般采用的_stdcall则是被调用者恢复堆栈(可变参数
函数调用除外)。
      至于返回值都是写入eax中,然后返回的。

    

     注:在Windows中,不管哪种调用方式都是返回值放在eax中,然后返回。外部从eax中得到返回值。

 

_cdecl方式下被调用函数需要做以下一些事情:

1)  保存 ebp。 ebp 总是被我们用来保存这个函数执行之前的 esp 的值。执行完毕之后,我们用 ebp 恢复 esp。同时,调用我们的上层函数也用 ebp 做同样的事情。所以先把 ebp 压入堆栈。返回之前弹出,避免 ebp 被我们改动。
2)  保存 esp 到 ebp 中。 上面两步的代码如下:

       
3)  在堆栈中腾出一个区域用来保存局部变量。这就是常说的所谓局部变量在栈空间中。方法为把 esp 减少一个数值。这样等于压入了一堆变量。日后要恢复时,只要把 esp 恢复成 ebp 中保存的数据就可以了。
4)  保存 ebx,esi,edi 到堆栈中。函数调用完后恢复。这是一个编程规范。 对应的代码如下:

       
5)  把局部变量区域初始化成 0cccccccch。0cccccccch 实际是 INT 3指令的机器码。这是一个中断指令

。因为局部变不可能被执行,如果执行了必然程序有错,这时发生中断来提示开发者。这是 VC 编译

DEBUG 版本的特有操作。
相关代码如下:

      
6)  然后做函数里应该做的事情。参数的获取是 ebp+8 字节为第一个参数,ebp+12 为第二个参数(注意
倒序压入),依次加。ebp+4 字节处是 要返回的地址。
7)  恢复 ebx,esi,edi ,esp,ebp,最后返回。 代码如下:
      

为了简单起见,我的函数没有返回值。如果要返回值,函数应该在返回之前,把返回值放入 eax 中。外

部通过 eax 得到返回值。 所以用 VC2003 编译 Debug 版本,反汇编代码如下:
      

而这个函数的调用方式是:
      

这样一来,函数调用的过程就很清楚了。

总结:

EBP在地址高端,ESP在地址低端。函数参数在EBP的正偏移处,函数内部局部变量在EBP的负偏移处。调用函数时,参数是倒序压栈(从右到左)。

函数被调用过程(以myfunction为例):

1)保存调用者的EBP,将ESP赋给EBP;

2)ESP减小一个范围,用来存放函数内部的局部变量;

3)保存EBX, ESI, EDI;

4)堆栈区内的局部变量区初始化为0cch(对应int 3中断),访问不存在的局部变量会引发该中断;

5)EBP+X访问函数参数,EBP-X分配函数局部变量;

6)返回值放在EAX中;

7)恢复EDI, ESI, EBX, EBP;

8)调用者:恢复堆栈,清楚堆栈内部被调用函数的函数参数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

johnlxj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值