<!-- @page { margin: 0.79in } P { margin-bottom: 0.08in } -->
先写一段程序,用的编辑环境为 Netbeans 6.9, gcc, gdb:
int main(int argc, char** argv) {
int i=1;
i++;
return (EXIT_SUCCESS);
}
编译为汇编指令为
!int main(int argc, char** argv) {
main+1: mov %esp,%ebp
! int i=1;
main()
main+6: movl $0x1,-0x4(%ebp)
! i++;
main+13: addl $0x1,-0x4(%ebp)
! return (EXIT_SUCCESS);
main+17: mov $0x0,%eax
!}
main+23: ret
!应该为注释,标记正在进行到代码的哪一个步骤
看 main+0: push %ebp。
ebp是基址指针寄存器。这里应该是说将原来的 ebp压入栈,这里的 ebp应该是作为一个栈的基地址而从栈里面取地址就要用 ebp做基准,所以这里存入上一级栈的 ebp。
看: main+1: mov %esp,%ebp
这里把 esp的值传给 ebp,也就是说 ebp现在可以当作目前这一级栈的基地址,而因为 esp经常改变,所以要用 ebp来做基址。
看 main+3: sub $0x10,%esp
这里因为有一个局部变量 i,所以要把 esp向上调,不知道 esp是不是就是指栈顶的意思?这里貌似还要 RoundUp变到 16 的倍数,所以总是 0xn0。如果有 5个局部变量,就会移动 x020。(这里还是有问题,见下一个例子)
看 main()
看 main+6: movl $0x1,-0x4(%ebp)
这里可以看到将 1付给 i。这里用到了偏址 4,得到 i的指,这里很奇怪为什么 i在那个位置。注意这里只有当运行到这一个语句的时候才会将 i的值压栈,而 esp会预留全部的局部变量的空间。
看 main+13: addl $0x1,-0x4(%ebp)
这里将 i的值加上 1。
看 main+17: mov $0x0,%eax
这里将 0付给 eax寄存器,可见这里 eax是存储返回值的寄存器。
看: main+22: leave
main+23: ret
这里应该是函数返回。
如果有一个语句为 j=i+j
则对应于汇编为
main+24: mov -0x4(%ebp),%eax//将 i的值付给 eax。
main+27: add %eax,-0x8(%ebp) //将 eax付给 j。可见这里 eax也是可以作为临时寄存器。
继续编一段程序:
int add(int i,int p){
int j=i+p;
return j;
}
int main(int argc, char** argv) {
int i=1;
i=add(i,8);
return (i);
}
main+0: push %ebp
main+1: mov %esp,%ebp
main+3: sub $0x18,%esp
! int i=1;
main+6: movl $0x1,-0x4(%ebp)
!
! i=add(i,8);
main+13: movl $0x8,0x4(%esp)
main+21: mov -0x4(%ebp),%eax
main+24: mov %eax,(%esp)
main+27: call 0x80483b4 <add>
main()
main+32: mov %eax,-0x4(%ebp)
!
! return (i);
main+35: mov -0x4(%ebp),%eax
!}
main+38: leave
main+39: ret
!int add(int i,int p){
add()
add+0: push %ebp
add+1: mov %esp,%ebp
add+3: sub $0x10,%esp
! int j=i+p;
add+9: mov 0x8(%ebp),%edx
add+12: lea (%edx,%eax,1),%eax
add+15: mov %eax,-0x4(%ebp)
! return j;
add+18: mov -0x4(%ebp),%eax
!}
add+21: leave
add+22: ret
看 main+13: movl $0x8,0x4(%esp)
main+21: mov -0x4(%ebp),%eax
main+24: mov %eax,(%esp)
这里其实就是把参数压栈,把 8和 i压进去。
看 main+27: call 0x80483b4 <add>
这里调用 add函数。
add+0: push %ebp
add+1: mov %esp,%ebp
add+3: sub $0x10,%esp
还是和 main函数开头一样的操作。存储旧的 ebp值压栈。这里刚开头的时候其实 esp的值已经变掉了从 0x560变到了 0x558,这里应该是 call指令完成的。
看 add+6: mov 0xc(%ebp),%eax
add+9: mov 0x8(%ebp),%edx
add+12: lea (%edx,%eax,1),%eax
add+15: mov %eax,-0x4(%ebp)
这里计算那个表达式的值。
最后返回。
这里我把栈的图画一下。
esp 0x548
0xbf899554 j
ebp 0xbf899558 0xbf899578(上一级的 ebp值 )
0xbf89955c 0x8048431(这里己有可能是调用着执行 call时的下一条语句)
esp 0xbf899560 i
0xbf899564 8
0xbf899574 i
esp 0xbf899578 上一级的 ebp。
但是如果我把 add函数写成这样
int add(int i,int p){
int j=1;
printf("i:%p\n",&i);
printf("p:%p\n",&p);
printf("j:%p\n",&j);
return j;
}
结果是:
i:0xbffff550
p:0xbffff554
j:0xbffff53c
发现局部变量和函数参数之间有四个空即 16个字节。发现其实 j并不是压在 ebp上一格的,而是 (ebp+12),这个很诡异。所以会出现在这种情况,我原来还以为是局部变量与参数间空 16个字节放各种 pointer以及返回值这些东西呢,这个实在是太诡异了。
宗其所述:正常情况下,局部变量与参数间空了 8个字节(linux下)。