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. eslint使用

    参考文档 http://www.cnblogs.com/hahazexia/p/6393212.html http://blog.guowenfh.com/2016/08/07/ESLint-Rule ...

  2. 从实践的角度理解cookie的几个属性

    cookie的处理流程大致分为以下几步: 1.浏览器初次请求服务器. 2.服务器认为有必要设置cookie,通过响应报文首部:Set-Cookie告知浏览器,cookie的内容. 3.浏览器本地保存( ...

  3. spring两大核心对象IOC和AOP(新手理解)

    现在自己对spring的理解是:spring的主要的作用是用来进行业务的处理和实现类与类之间的解耦. 其中实现解耦功能 是IOC(控制反转)通过sessionfactory工厂来为需要的对象注入对应的 ...

  4. WPF 设置输入只能英文

    有时输入只能让用户输入英文,那么如何设置输入只能英文? 首先在xaml 写一个 TextBox ,给他一个名字. <TextBox x:Name="txt"></ ...

  5. 前端工程化grunt

    1.grunt是什么? grunt是基于nodejs的前端构建工具.grunt用于解决前端开发的工程问题. 2.安装nodejs Grunt和所有grunt插件都是基于nodejs来运行的. 安装了n ...

  6. java 入门之八大内置基本类型

    本文采用eclipse 工具演示,如果您对eclipse 工具不了解,请先学习下 eclipse 工具的使用,这个里面只是简单的介绍下输出和注释: 安装完成eclipse 以后,双击进入 后一次点击 ...

  7. python第三课

    本节内容 1.列表 2.购物车设计思路 3.字典 1.列表 不可变类型:整型.字符串.元组tuple 可变类型:列表list.字典dict 2.购物车 3.字典

  8. Python学习笔记(二)-Python文件类型及编程模式

    Python环境搭建:linux,Windows... Linux下:[root@localhost StudyPython]# python #进入交互模式Python 2.7.11 (defaul ...

  9. linux root修改密码失败

    问题: 当使用root修改密码时,报错passwd:Authentication token manipulation error 解决: 1.查看是否权限问题, /etc/passwd /etc/s ...

  10. Python BDD自动化测试框架初探

    1. 什么是BDD BDD全称Behavior Driven Development,译作"行为驱动开发",是基于TDD (Test Driven Development 测试驱动 ...