1、 Debug模式下,VC++6.0下断点运行,按CTRL+F11可查看汇编代码;另外可以用cl /c /FAs YourCppFile.cpp命令行在同目录生成YourCppFile.asm汇编文件。

2、 Push将32位操作数压入堆栈,esp指向栈顶,故esp减去4(字节=32位,在64位机器上则是8)。记住:esp为栈顶指针,堆栈越高,这个值越小。由于Intel处理器采用小端存储模式,故当PUSH一个寄存器(如EBP)的值0012FFB4时,先存低字节再存高字节,即堆栈从栈顶向下应该是B4FF1200,当然,采用LONG型HEX解析内存时显示的数值将不变,仍是0012FFB4。

3、 CALL的本质相当于PUSH+JMP(压入CALL语句下一条指令地址,然后跳转至CALL指定的函数入口地址),RET的本质相当于POP+JMP(弹出CALL语句下一条指令地址,然后跳转该地址)。

4、 对堆栈的操作指令除了PUSH、POP、CALL、RET以外,还可以是ADD,SUB。在C语言中局部变量是保持在栈里,可以用esp – 4*4的方式在堆栈中分配4个4字节长的整形空间,而不用PUSH4次。

5、 用XOR EAX,EAX来取代MOV EAX,0,这样来实现清零的好处是:速度更快,占用字节更少。

6、 LEA取址运算符,但如下语句:LEA EDI,[EBP - 0CCH]与MOV EDI,EBP - 0CCH在语义上是等同的([X]表示存储器X中的内容),但是后者会导致语法错误,原因是MOV不支持后一个操作数为寄存器减去一个数字的形式

7、 C语言函数调用默认方式(_cdcall):调用方把参数反序(从右到左)压入堆栈,被调用方把堆栈复原(获取参数)。这些参数须对齐到机器字长,32、64位CPU分别对齐到4、8个字节。

8、 右3可知,CALL和RET并不是函数存在的绝对证据,我们可以自行操作堆栈然后使用JMP绝对跳转实现函数调用。由于高级语言对函数调用的规则各不一样,由此产生了调用约定,在windows上有:Pascal方式、WINAPI方式(_stdcall)、C方式(_cdcall)。

9、 Pascal函数调用方式基本用在16位函数库中,现在基本不用。

10、_stdcall函数调用方式规则(Windows API):参数从右到左入栈;被调用的函数在返回前自行清理堆栈(可变参数函数调用除外)。

11、_cdcall函数调用方式规则(C编译器默认):参数从右到左入栈;函数返回后,调用者负责清理堆栈,由此通常生成较大可执行文件

12、不管何种调用方式,返回值都放入EAX寄存器中。

13、_cdcall函数:

 int MyFunction(int a,int b)
 {
     return a+b;
 }  

汇编后代码为:

 int MyFunction(int a,int b)
 {
 0040D430 push   ebp                     ;1)
 0040D431 move   ebp,esp                  ;2)
 0040D433 sub    esp,40h                 ;3)
 0040D436 push   ebx
 0040D437 push   esi
 0040D438 push   edi                     ;4)
 0040D439 lea    edi,[ebp-40h]
 0040D43C mov    ecx,10h
 0040D441 mov    eax,0CCCCCCCCh
 0040D446 rep stos dword ptr [edi]       ;5)
     return a+b;
 ]
 0040D44B add    eax,dword ptr [ebp+0Ch] ;6)
 }
 0040D44E pop    edi
 0040D44F pop    esi
 0040D450 pop    ebx                     ;7)
 0040D451 mov    esp,ebp
 0040D453 pop    ebp                     ;8)
 0040D454 ret      

它所做的事情有:

1) 保存EBP(基址寄存器)。保存之前的ESP值,在返回时恢复,使函数对堆栈能够实现正确操作。

2) 保存ESP到EBP,此时两者相等,都是函数调用以后的栈基址(栈顶)。

3) 在栈中腾出40H的字节区域用来保存局部变量。大小是可变的,由编译器自动分配。

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

5) 初始化局部变量区域,循环10H次,每次赋值4个字节(共10H * 4 = 40H字节,与上面分配的字节区域刚好对应)为0CCCCCCCCH,这个值实际上是INT3的机器码,是一个断点中断指令,因局部变量区域不可能被执行,如果执行了则报错。

6) 第一个参数获取位置为ebp + 双倍CPU字长,这里是ebp + 8,后面的依据类型进行偏移。需要说明一点的是,调用者在调用前将参数倒序压入堆栈,所有参数压入以后,在执行CALL指令时,它会自动将CALL指令下面的一条指令地址压入堆栈;此外,进入调用的函数以后,第一件事就是压入EBP到堆栈。由此可以看出,函数当前栈顶(ESP指向位置)与最后一个压入栈的参数(参数列表的第一个参数)相隔了两个CPU字长。这里是4 * 2 =8个字节,故为ebp + 8。

7) 恢复ebx,esi,edi。这里是与入口对应的现场恢复,没什么好说的

8) EBP是被调用者栈顶指针,其内存单元的值是调用者栈顶指针。所以,这里一方面是使ESP指向被调用者的栈顶(也是调用者的栈底),另一方面是恢复调用者的栈基址。

9) RET执行函数返回,此时,ESP自动加上一个CPU字长,指向最后一个被压入的参数位置。

10)  函数的返回值在EAX中,这里不需要额外操作,如果结果不是在EAX,则在返回前一定有MOV操作(或其等同效果的操作)

14、对上面的程序执行如下调用:

 int main(void)
 {
     MyFunction(,);
     ;
 }  

其汇编代码为:

 int main(void)
 {
 0040D3F0 push   ebp             ;a)
 0040D3F1 mov    ebp,esp
 0040D3F3 sub    esp,40h             ;b)
 0040D3F6 push   ebx             ;c)
 0040D3F7 push   esi             ;d)
 0040D3F8 push   edi             ;e)
 0040D3F9 lea    edi,[ebp-40h]
 0040D3FC mov    ecx,10h
 0040D401 mov    eax,0CCCCCCCCh
 0040D406 rep stos dword ptr [edi]
     MyFunction(,);
                ;f)
                ;g)
 (MyFunction) (0040100a)   ;h)  

     return ;
 0040D414 xor    eax,eax
 }
 0040D416 pop    edi
 0040D417 pop    esi
 0040D418 pop    ebx
 0040D419 add    esp,40h
 0040D41C cmp    ebp,esp
 0040D41E call   __chkesp (0040d460)
 0040D423 mov    esp,ebp
 0040D425 pop    ebp
 0040D426 ret  

对以上代码执行时的内存单元数据情况如下表:

操作
堆栈地址 HEX数据 描述 EBP寄存器
     
4) 0012FED8 0012FF80 MyFunction函数压入edi 0012FF24
4) 0012FEDC 01F8BCE8 MyFunction函数压入esi 0012FF24
4) 0012FEE0 7FFDF000 MyFunction函数压入ebx 0012FF24
3)、5)

0012FEE4

0012FF20

CCCCCCCC

CCCCCCCC

MyFunction函数压入预留局部变量存储区,全部初始化为0xCCCCCCCCH 0012FF24
1)、2) 0012FF24 0012FF80 MyFunction函数压入ebp 0012FF24
h) 0012FF28 0040D411 mian函数CALL调用,压入返回地址 0012FF80
g) 0012FF2C 00000001 参数1入栈 0012FF80
d) 0012FF30 00000002 参数2入栈 0012FF80
e) 0012FF34 01DD0000 main函数压入edi 0012FF80
d) 0012FF38 01F8BCE8 main函数压入esi 0012FF80
c) 0012FF3C 7FFDF000 main函数压入ebx 0012FF80
b)

0012FF40

0012FF7C

CCCCCCCC

CCCCCCCC

main函数压入预留局部变量存储区,全部初始化为0xCCCCCCCCH 0012FF80
a) 0012FF80 0012FFC0 main函数压入ebp 0012FF80

函数地用过程中的操作由下往上看,注意CALL与RET指令执行时的堆栈变化。其中红色和蓝色为EBP寄存器变化情况,灰绿色为参数压栈。

15、 需要注意的是,以上的汇编代码来自于VC++6.0编译器,在自行写汇编或者嵌入汇编时要稍做改变。比如说CALL指令直接写为call MyFunction

《天书夜读:从汇编语言到windows内核编程》一 汇编指令与C语言的更多相关文章

  1. 《天书夜读:从汇编语言到windows内核编程》三 练习反汇编C语言程序

    1) Debug版本算法反汇编,现有如下3×3矩阵相乘的程序: #define SIZE 3 int MyFunction(int a[SIZE][SIZE],int b[SIZE][SIZE],in ...

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

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

  3. 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作

    1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...

  4. 《天书夜读:从汇编语言到windows内核编程》六 驱动、设备、与请求

    1)跳入到基础篇的内核编程第7章,驱动入口函数DriverEnter的返回值决定驱动程序是否加载成功,当打算反汇编阅读驱动内核程序时,可寻找该位置. 2)DRIVER_OBJECT下的派遣函数(分发函 ...

  5. 《天书夜读:从汇编语言到windows内核编程》十一 用C++编写内核程序

    ---恢复内容开始--- 1) C++的"高级"特性,是它的优点也是它的缺点,微软对于使用C++写内核程序即不推崇也不排斥,使用C++写驱动需注意: a)New等操作符不能直接使用 ...

  6. 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建

    1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...

  7. 《天书夜读:从汇编语言到windows内核编程》十 线程与事件

    1)驱动中使用到的线程是系统线程,在system进程中.创建线程API函数:PsCreateSystemThread:结束线程(线程内自行调用)API函数:PsTerminateSystemThrea ...

  8. 《天书夜读:从汇编语言到windows内核编程》九 时间与定时器

    1)使用如下自定义函数获取自系统启动后经历的毫秒数:KeQueryTimeIncrement.KeQueryTickCount void MyGetTickCount(PULONG msec) { L ...

  9. 《天书夜读:从汇编语言到windows内核编程》七 内核字符串与内存

    1)驱动中的字符串使用如下结构: typedef struct _UNICODE_STRING{ USHORT Length; //字符串的长度(字节数) USHORT MaximumLength; ...

  10. 《天书夜读:从汇编语言到windows内核编程》二 C语言的流程与处理

    1) Debug与Release的区别:前者称调试版,后者称发行版.调试版基本不优化,而发行版会经过编译器的极致优化,往往与优化前的高级语言执行流程会大相径庭,但是实现的功能是等价的. 2) 如下fo ...

随机推荐

  1. Java公开课-01.类和对象

    一,类和对象的含义 1.类:类是具有相同属性(静态特征)和行为(功能 )的一系列事物的集合. eg:以下俩者是不是类 1)汽车  √ 2)小胖桌子上那个红色的杯子  × 2.对象:被精确限定到一个特殊 ...

  2. 不同Activity之间传递线程

    场景:Android由Activiy A创建Activiy B时 ,A创建的线程B中依然需要调用,这时候需要在两个activity之间传递线程的信息. 解决: 方式一:线程自己维护自己的静态句柄(比较 ...

  3. IDL 矩阵运算

    矩阵相乘,A#B表示A的列乘以B的行,要求A的行数必须跟B的列数一致 IDL> A=[[0,1,2],[3,4,5]] IDL> B=[[0,1],[2,3],[4,5]] IDL> ...

  4. CentOS 6.5 中安装 Mysql 5.6,并远程连接Mysql

    ι 版权声明:本文为博主原创文章,未经博主允许不得转载. 1.在安装CentOS时,若选择的是Basic Server(可支持J2EE开发),则新安装好的CentOS系统中默认是已经安装了一个mysq ...

  5. 吾八哥学Python(四):了解Python基础语法(下)

    咱们接着上篇的语法学习,继续了解学习Python基础语法. 数据类型大体上把Python中的数据类型分为如下几类:Number(数字),String(字符串).List(列表).Dictionary( ...

  6. Ubuntu配置OpenStack 一:主机环境配置以及问题总结

    本文包含openstack配置的实验环境的基本步骤.在下面的步骤中将逐步讲解如何操作. 1.准备三台虚拟机 主机名字分别命名为controller.network.computer[desktop版或 ...

  7. Zookeeper 笔记-角色

    leader:提供读写服务,负责投票的发起和决议,更新系统状态 follower:为客户提供读服务,如果写服务则转发给leader,选举过程参与投票 observer:为客户端提供读服务,如果是写服务 ...

  8. JAVA基础知识总结:二

    一.数据类型 1.常量 在程序运行的过程中,值不会发生改变的标识符 常量的分类:整数常量.小数常量.布尔值常量.字符常量.字符串常量.null常量 2.变量 表示的值可以发生改变 定义一个变量,需要在 ...

  9. Java基础总结--常用类以及包的访问权限

    -----Object---所有类的根类1.怎么得到的:通过对所有对象不断的向上抽取共性,具备所有对象的共性的东西2.常用的方法* equals(Object obj):比较两个对象的引用是否指向同一 ...

  10. jsp页面接收json字符串

    jsp页面接收 后台添加