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中得到返回值。

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

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

(2)保存esp到ebp中。

上面两步的代码如下:

;保存ebp,并把esp放入ebp中,此时ebp和esp同

;都是这次函数调用时的栈顶

00411360  push        ebp

00411361  mov         ebp,esp

(3)在堆栈中腾出一个区域用来保存局部变量,这就是常说的所谓局部变量时保存在栈空间中的。方法是:把esp减少一个数值,这样就等于压入了一堆变量。恢复时,只有把esp恢复成ebp中保存的数据就行。

(4)保存ebx、esi、edi到堆栈中,函数调用完后恢复。

上面两步对应代码如下:

;把esp往下移动一个范围,等于在堆栈中放出一片新

;的空间来保存局部变量

00411363  sub         esp,0CCh

00411369  push        ebx

0041136A  push        esi

0041136B  push        edi

(5)把局部变量区域初始化成全0CCCCCCCCh。0CCh实际上是int3指令的机器码,这是一个断点中断指令。因为局部变量不可能被执行,如果执行了,必然程序出错,这时发生中断来提示开发者。这时VC编译Debug版本的特有操作:

0041136C  lea         edi,[ebp-0CCh]

00411372  mov         ecx,33h

00411377  mov         eax,0CCCCCCCCh

0041137C  rep stos    dword ptr es:[edi]

(6)然后做函数里应该做的事情。参数的获取是ebp+12字节为第二个参数,ebp+8字节为第一个参数(反序入栈),依次增加。最后ebp+4字节处是要返回的地址。

(7)恢复ebx、esi、edi、esp、ebp,最后返回:

00411387  pop         edi

00411388  pop         esi

00411389  pop         ebx

0041138A  mov         esp,ebp

0041138C  pop         ebp

0041138D  ret

用VS2010编译Debug版本,完整的反汇编代码如下:

--- g:/acecore/acecore/acecore.cpp ---------------------------------------------

// ACECore.cpp : Defines the entry point for the console application.

//

#include "stdafx.h"

void ACEFunction(int a, int b)

{

00411360  push   ebp  ;保存ebp,并把esp放入ebp中。此时ebp与esp相同

00411361  mov   ebp,esp  ;都是这次函数调用时的栈顶

00411363  sub   esp,0CCh  ;把esp往上移动一个范围,等于在堆栈中放出一片新

;的空间用来存储局部变量

00411369  push   ebx  ;下面保存三个寄存器:ebx、esi、edi

0041136A  push   esi

0041136B  push   edi

0041136C  lea   edi,[ebp-0CCh] ;原本是想使用“mov edi, ebp-0CCh”,但是mov不支持

;“-”操作,所以先对ebp-0CCh取内容(即[ebp-0CCh]),

;再利用lea把[ebp-0CCh]的地址也就是ebp-0CCh放到edi中

;目的是把保存局部变量的区域(即ebp-0CCh开始的区域)

;初始化成全部为0CCCCCCCCh。

00411372  mov   ecx,33h

00411377  mov   eax,0CCCCCCCCh

0041137C  rep stos    dword ptr es:[edi]  ;写入0CCh指令(中断)

int c = a + b;

0041137E  mov   eax,dword ptr [a]  ;加法操作,从堆栈中取得从外部传入的参数。

00411381  add   eax,dword ptr [b]  ;通过ida反汇编可以看到,其实这两天指令是:

;mov eax, [ebp+8]

;add eax, [ebp+0Ch]

;参数是通过ebp从堆栈中取得的。这里看到的是

;VC调试器的显示结果,是为了方便阅读,

;直接加上了参数名

00411384  mov  dword ptr [c],eax

}

00411387  pop         edi  ;恢复edi、dsi、ebx

00411388  pop         esi

00411389  pop         ebx

0041138A  mov         esp,ebp  ;恢复原来的ebp和esp,让上一级调用的

0041138C  pop         ebp  ; 函数可以正常使用

0041138D  ret

主程序中对这个函数的调用方式是:

004123DC  mov         eax,dword ptr [b]  ;把b, a两个参数压入堆栈

004123DF  push        eax

004123E0  mov         ecx,dword ptr [a]

004123E3  push        ecx

004123E4  call        ACEFunction (411014h)  ;调用函数ACEFunction

004123E9  add         esp,8  ;恢复堆栈

Windows内核 基本汇编指令的更多相关文章

  1. 学习linux内核时常碰到的汇编指令(1)

     转载:http://blog.sina.com.cn/s/blog_4be6adec01007xvg.html 80X86 汇编指令符号大全 +.-.*./∶算术运算符. &∶宏处理操作符. ...

  2. 学习windows内核书籍推荐 ----------转自http://tieshow.iteye.com/blog/1565926

      虽然,多年java,正在java,看样子还得继续java.(IT小城,还是整java随意点)应用程序 运行于操作系统之上,  晓操作系统,方更晓应用程序. 主看windows,因为可玩性高,闭源才 ...

  3. Windows Kernel Way 1:Windows内核调试技术

    掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功 ...

  4. 《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建

    (原书)所有内核空间共享,DriverEntery是内核程序入口,在内核程序被加载时,这个函数被调用,加载入的进程为system进程,xp下它的pid是4.内核程序的编写有一定的规则: 不能调用win ...

  5. 【嵌入式开发】 ARM 汇编 (指令分类 | 伪指令 | 协处理器访问指令)

    作者 : 韩曙亮 博客地址 : http://blog.csdn.net/shulianghan/article/details/42408137 转载请著名出处 本博客相关文档下载 :  -- AR ...

  6. Windows内核编程时的习惯与注意事项

    Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html 一.内核编程注意细节: 在头文件中使用的是 <ntddk.h ...

  7. Windows内核基础知识-5-调用门(32-Bit Call Gate)

    Windows内核基础知识-5-调用门(32-Bit Call Gate) 调用门有一个关键的作用,就是用来提权.调用门其实就是一个段. 调用门: 这是段描述符的结构体,里面的s字段用来标记是代码段还 ...

  8. Windows内核中的CPU架构-6-中断门(32-Bit Interrupt Gate)

    Windows内核中的CPU架构-6-中断门(32-Bit Interrupt Gate) 中断门和调用门类似,也是一种系统段.同样的它也可以用来提权. 中断门: 虽然中断门的段描述符如下: 但是中断 ...

  9. 【转载】64 位 Windows 内核虚拟地址空间布局(基于 X64 CPU)

    原文链接:http://shayi1983.blog.51cto.com/4681835/1734822 本文为原创翻译,原文出处为 http://www.codemachine.com/articl ...

随机推荐

  1. spring优化使用

    1.bean由框架填充,避免手写优化代码. 2.view的展示通过配置或注解实现最优化使用架构. 待续...

  2. c#中ref和out 关键字

    问题:为什么c#中要有ref和out?(而java中没有)需求假设:现需要通过一个叫Swap的方法交换a,b两个变量的值.交换前a=1,b=2,断言:交换后a=2,b=1. 现编码如下: class ...

  3. UVa12298 Super Poker II(母函数 + FFT)

    题目 Source http://acm.hust.edu.cn/vjudge/problem/23590 Description I have a set of super poker cards, ...

  4. mysql修改默认编码为UTF8

    Linux下一般是 /etc/my.cnf --在 [mysqld] 标签下加上三行default-character-set = utf8character_set_server = utf8 -- ...

  5. Java 读取配置文件 Properties

    String filePath="src/cn/ac/iscas/pebble/ufe/conf/id.properties"; InputStream in = new Buff ...

  6. BZOJ 1191 超级英雄 Hero 题解

    BZOJ 1191 超级英雄 Hero 题解 Description 现在电视台有一种节目叫做超级英雄,大概的流程就是每位选手到台上回答主持人的几个问题,然后根据回答问题的多少获得不同数目的奖品或奖金 ...

  7. 【BZOJ】2563: 阿狸和桃子的游戏

    http://www.lydsy.com/JudgeOnline/problem.php?id=2563 题意:给一个n个加权点m条加权边的无向图,两个人轮流拿走一个点,最后使先手得分-后手得分尽量大 ...

  8. HDU 4003 Find Metal Mineral(分组背包+树形DP)

    题目链接 很棒的一个树形DP.学的太渣了. #include <cstdio> #include <string> #include <cstring> #incl ...

  9. PHP面向对象学习五 类中接口的应用

    类中接口的应用 接口:一种成员属性全部为抽象的特殊抽象类,在程序中同为规范的作用   抽象类:1.类中至少有一个抽象方法.2.方法前需要加abstract 接口: 1.类中全部为抽象方法,抽象方法前不 ...

  10. solrcloud线上创建collection,修改默认配置

    一.先看API,创建collection 1.上传配置文件到zookeeper 1) 本地内嵌zookeeper集群:java -classpath ./solr-webapp/webapp/WEB- ...