用汇编的眼光看C++ (之x86汇编)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
说到用汇编的眼光看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
大家有没有想过,如果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
前面我们说过,在函数内部的所有变量都会保存在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: }
那么函数在返回前做了一些什么呢?
- 46: }
- 004012FF pop edi
- 00401300 pop esi
- 00401301 pop ebx
- 00401302 mov esp,ebp
- 00401304 pop ebp
- 00401305 ret
那么函数调用的时候,入参是怎么处理的呢?
- 53: process(20);
- 0040EFA4 push 14h
- 0040EFA6 call @ILT+40(process) (0040102d)
- 0040EFAB add esp,4
| 函数参数 |
| 返回地址 |
| 临时变量 | <------------------------ ebp
| 压栈寄存器 |
| 栈顶 | <-------------------------esp
其他知识:
(1) 全局运算cpu寄存器很多,一般有eax,ebx,ecx,edx等等。我们通常说的ax,bx,cx,dx指的只是他们的低位部分。
(2)段寄存器寄存了程序的代码段,数据段和堆栈段。代码段保存了全部的程序代码,数据段保存了全据变量的代码,而堆栈则是全部堆栈空间。
(3)目前 vc编译器支持嵌入式汇编,大家有兴趣的话可以在函数内部试试身手。下面的代码只是一个范例:
- void process(int* q)
- {
- _asm {
- push eax
- push ebx
- push ecx
- mov eax, 0x10
- mov ebx, 0x15
- add eax, ebx
- mov ecx, q
- mov [ecx], eax
- pop ecx
- pop ebx
- pop eax
- }
- }
(全部完)