一、CPU指令解析
最常用的mov指令
指令中最常使用的是对寄存器和内存进行数据存储的 mov 指定数据的存储地和读出源。操作数中可以指定寄存器、常数、标签(附加在地址前),以及用方括号([ ])围起来的这些内容。如果指定了没有用([ ])方括号围起来的内容,就表示对该值进行处理;如果指定了用方括号围起来的内容,方括号的值则会被解释为内存地址,然后就会对该内存地址对应的值进行读写操作。让我们对上面的代码片段进行说明指令,mov指令的两个操作数,分别用来
mov ebp,espeax,dword ptr [ebp+8]
mov ebp,esp中,esp 寄存器中的值被直接存储在了ebp中,也就是说,如果esp 寄存器的值是100的话那么 ebp 寄存器的值也是100.
而在 mov eax,dword ptr [ebp+8]这条指令中,ebp寄存器的值+8后会被解析称为内存地址。如果 ebp
寄存器的值是100的话,那么eax寄存器的值就是100+8的地址的值。dword ptr 也叫做double word pointer 简单解释一下就是从指定的内存地址中读出4字节的数据
对栈进行push和pop
程序运行时,会在内存上申请分配一个称为栈的数据空间。栈(stack)的特性是后入先出,数据在存储时是从内存的下层(大的地址编号)逐渐往上层(小的地址编号)累积,读出时则是按照从上往下进行读取的。
栈的模型:
栈的存储临时数据的区域,它的特点是通过 push 指令和pop指令进行数据的存储和读出。向栈中存储数据称为 入栈 ,从栈中读出数据称为 出栈 ,32位 x86 系列的CPU中,进行1次push或者pop,即可处理32位(4字节)的数据
二、函数的调用机制
下面我们一起来分析一下函数的调用机制,我们以上面的C语言编写的代码为例。首先,让我们从MyFunc 函数调用 AddNum 函数的汇编语言部分开始,来对函数的调用机制进行说明。栈在函数的调用中发挥了巨大的作用,下面是经过处理后的MyFunc函数的汇编处理内容
_MyFunc proc near push ebp ; 将ebp寄存器的值存入栈中 (1) mov ebp,esp ; 将esp寄存器的值存入ebp寄存器中 (2) push 456 ; 将456入栈 (3) push 123 ; 将123入栈 (4) call _AddNum ; 调用 AddNum 函数 (5) add esp,8 ; esp寄存器的值 + 8 (6) pop ebp ; 读出栈中的数值存入esp寄存器中 (7) ret ; 结束 MyFunc 函数,返回到调用源 (8) _MyFunc ebp
代码解释中的(1)、(2)、(7)、(8)的处理适用于C语言中的所有函数,我们会在后面展示 AddNum函数处理内容时进行说明。这里希望大家先关注(3)-(6)这一部分,这对了解函数调用机制至关重要。
(3)和(4)表示的是将传递给AddNum函数的参数通过push入栈。在C语言源代码中,虽然记述为函数AddNum(123, 456),但入栈时则会先按照456, 123这样的顺序。也就是位于后面的数值先入栈。这是C语言的规定。(5)表示的call 指令,会把程序流程跳转到AddNum函数指令的地址处。在汇编语言中,函数名 表示的就是函数所在的内存地址。AddNum 函数处理完毕后,程序流程必须要返回到编号(6)这一行。call 指令运行后,call 指令的下一行(也就指的是(6)这一行)的内存地址(调用函数完毕后要返回的内存地址)会自动的push入栈。该值会在AddNum函数处理的最后通过ret指令pop出栈,然后程序会返回到(6)这一行。
(6)部分会把栈中存储的两个参数(456和123)进行销毁处理。虽然通过两次的pop指令也可以实现,不过采用esp 寄存器+8的方式会更有效率(处理1次即可)。对栈进行数值的输入和输出时,数值的单位是4字节。因此,通过在负责栈地址管理的esp 寄存器中加上4的2倍8,就可以达到和运行两次 pop命令同样的效果。虽然内存中的数据实际上还残留着,但只要把esp 寄存器的值更新为数据存储地址前面的数据位置,该数据也就相当于销毁了
我在编译 Sample4.c 文件时,出现了下图的这条消息
D: \C> bcc32-c-S Sample4.cBorland C++ 5. 5. 1 for Win32 Copyright <c> 1993.2000 BorlandSample4.c:Warning W8004 Sample4.c 10: 'c' is assigned a value that is never used in function MyFunc
上面的意思是指c的值在MyFunc定义了但是一直未被使用,这其实是一项编译器优化的功能,由于存储AddNum函数返回值的变量c在没有被用到,因此编译器就认为 该变量没有意义,进而也就没有生成与之对应的汇编语言代码
下图是调用AddNum这一函数前后栈内存的变化: