说到用汇编的眼光看C++语言,那么怎么阅读汇编代码就成了我们需要解决的一个问题。其实,实话说,汇编其实不难。只是我们需要明白这样几个问题:
(1)汇编是什么语言?
(2)汇编中的主要内容有哪些?
(3)汇编语言是怎么和实际C/C++语言代码一一对应的?
(1)汇编是什么语言
其实汇编语言是CPU指令码的一种标记符号。不同的CPU具有不同的指令集,普通PC上的CPU一般来自AMD或者是INTEL,使用的也就是我们今天所要说的X86指令集。其他类似的CPU还有POWERPC,主要来自电信企业的交换机、路由器使用;ARM类型,主要是智能终端或者仪器仪表类的设备使用;SUN SPARC类型,主要来供SUN服务器使用。因为CPU指令集和二进制码几乎是一一对应的,所以汇编语言不但可以帮助我们快速了解机器的硬件,也方便我们了解程序是怎么在设备上面运行的。
(2)汇编语言的内容有哪些?
汇编语言的内容有很多,但是真正和我们C/C++语言相关的内容其实并不多。大体上你需要了解的只有寄存器、段地址、堆栈、寄存器之间的基本操作、地址访问这些就足够了。
(3)汇编语言是怎么和实际语言一一对应的?
我们从一个范例开始。一般来说,一条语句都需要拆分成若干条汇编语句。比如说下面这一段话:
int m = 10; int n = 20; int p = m + n; |
我们这里假设m、n、p都是处在一个函数之中,所以事实上三个变量都是临时变量,在进入到函数之前,ebp和esp之间都要腾出空间为这些临时变量做准备。那么这三句话应该是这样解释的。
43: int m = 10; 004012E8 mov dword ptr [ebp-4],0Ah 44: int n = 20; 004012EF mov dword ptr [ebp-8],14h 45: int p = m + n; 004012F6 mov eax,dword ptr [ebp-4] 004012F9 add eax,dword ptr [ebp-8] 004012FC mov dword ptr [ebp-0Ch],eax |
我们可以通过上面的代码直观地看到汇编语句和C语言之间的对应关系。第一句,m赋值为10,内存正是ebp向下的内存;第二句和第一句类似;第三句稍微有点复杂,我们可以分析一下。首先我们看到,CPU从堆栈中把m的数据找了出来,也就是[ebp-4]地址处的数据,接着CPU用同样的方法把n的数据也找了出来,直接加到寄存器eax上,最后一步比较简单,就是把eax的数据保存在[ebp-0c]处的地址上。只要是函数内部的临时变量,就会看到这样的形式。临时变量就是依靠ebp的偏移地址获取的。
大家有没有想过,如果p是全局变量呢?
45: int m = 10; 004012E8 mov dword ptr [ebp-4],0Ah 46: int n = 20; 004012EF mov dword ptr [ebp-8],14h 47: p = m + n; 004012F6 mov eax,dword ptr [ebp-4] 004012F9 add eax,dword ptr [ebp-8] 004012FC mov [p (0042b0b4)],eax |
看到上面的代码,我们发现m、n的赋值方向没有发生什么样的变化。变化的是,最后寄存器eax的数值被赋值给了一个绝对地址0x42b0b4。这说明了一个问题,在程序加载到内存后,全局变量是有独立地址空间,并不会随着堆栈的浮动发生变化。
前面我们说过,在函数内部的所有变量都会保存在ebp到esp之间的堆栈空间上,那么代码是怎么做的呢?我们可以看这样一段汇编代码?
41: void process() 42: { 004012D0 push ebp 004012D1 mov ebp,esp 004012D3 sub esp,4Ch 004012D6 push ebx 004012D7 push esi 004012D8 push edi 004012D9 lea edi,[ebp-4Ch] 004012DC mov ecx,13h 004012E1 mov eax,0CCCCCCCCh 004012E6 rep stos dword ptr [edi] 43: int m = 10; 004012E8 mov dword ptr [ebp-4],0Ah 44: int n = 20; 004012EF mov dword ptr [ebp-8],14h 45: int p = m + n; 004012F6 mov eax,dword ptr [ebp-4] 004012F9 add eax,dword ptr [ebp-8] 004012FC mov dword ptr [ebp-0Ch],eax 46: } |