1. _add_a_and_b:
2. push %ebx
3. mov %eax, [%esp+8]
4. mov %ebx, [%esp+12]
5. add %eax, %ebx
6. pop %ebx
7. ret
8. _main:
9. push 3
10. push 2
11. call _add_a_and_b
12. add %esp, 8
13. ret
首先程序从_main开始,也就是第九行。
push 3 意思是将3压入栈(栈的顶部是地址小的区域,压入栈的数据越多,esp越小)。注意在push之前是有前置操作的,由于3是int类型的所以是4个字节。所以esp得-4个字节,因为这边是从高位到低位所以是减法。
假设这段程序系统分配的内存地址是0x1000~0x0000;
那么现在寄存器里存放的内存地址由原先的0x1000变为0x0ffd
栈里对应分配4个字节的内存空间,下面是内存地址和值对应:
0x1000 0
0x0FFF 0
0x0FFE 0
0x0FFD 3
然后执行push 2 esp再次减4字节这个时候(esp存的内存地址为0x0FE9):
0x0FFC 0
0x0FFB 0
0x0FFA 0
0x0FE9 2
接下来执行第11行 call _add_a_and_b 的时候,call关键字会创建一个该函数用的帧。
第2行代码push %ebx 事先把原来ebx寄存器里面的值压入栈(因为需要借用寄存器ebx来进行计算,等会会将压入栈的内容还回来,相当于temp变量,push指令会再将 ESP 寄存器里面的地址减去4个字节(累计减去12)这个时候esp存的内存地址为0x0FE5)
执行第3行 mov %eax, [%esp+8] 将 esp内存地址(0x0FE5)加上8,这个时候esp内存地址是(0x0FFC),然后就是将0x0FFC~0x0FE9的值赋值给eax(为什么eax不用push呢因为eax用的比较频繁原来的值不保留也没关系)
那么eax里的四个字节,假设他们为字节A,字节B,字节C,字节D:
字节A=0
字节B=0
字节C=0
字节D=2
接下来执行第4行mov %ebx, [%esp+12] ,跟上面一样,把0x1000~0x0FFD中的内容给了ebx
第五行add %eax, %ebx
add指令的意思是将第二个参数也就是ebx的值和第一个参数eax的值相加复制到eax
执行完了以后
eax是{0,0,0,5}
ebx是{0,0,0,3}
执行第6行pop %ebx 将原先ebx中的值替换ebx现在的{0,0,0,3}变成{?,?,?,?}(因为在这段过程中我不知道ebx的原先值),既然push指令会减4个字节,那么pop也会加4个字节吧
第7行ret 是函数调用结束,回收帧
第12行. add %esp, 8 手动将esp寄存器的地址加回来,而使用的那些内存不会再用会被回收。
在这边回收的意思是值,我往内存里写入值不用在意内存里面是不是是有值的。我只要告诉寄存器内存地址就行了。如果上面寄存器的内存地址不加8加回来,那么0x1000~0x0FE9的内存就永远空置在那里了,从而也可以理解为什么delphi debug的时候有些内存有奇怪的值了。