昨天同事调试的时候,发现的一个问题,比较有意思,拿来与大家分享:
pop (%esp)指令的执行结果是怎么样的?
先测试一下:
#include<stdio.h>
int main() { __asm__("pop (%esp)");
return 0; }
|
我们用gdb调试一下:
(gdb) b main Breakpoint 1 at 0x8048352: file t_esp.c, line 5. (gdb) r Starting program: /home/wangyao/a.out
Breakpoint 1, main () at t_esp.c:5 5 __asm__("pop (%esp)"); (gdb) x $esp 0xbf876f64: 0xbf876f80 (gdb) x $esp+4 0xbf876f68: 0xbf876fd8 (gdb) x $esp+8 0xbf876f6c: 0xb7de1050 (gdb) s 8 return 0; (gdb) x $esp 0xbf876f68: 0xbf876f80 (gdb) x $esp+4 0xbf876f6c: 0xb7de1050 (gdb) q
|
结果,我们发现原来%esp中的内容,被弹到了%esp+4中了。
刚开始的时候,我感到很诧异,但是在VC6和gdb下调试都是一个样子的结果,已经不是OS的原因了。那只有是CPU硬件的原因了。
本来以为是CPU的BUG,时序有问题,但是同事讲了一下他的理解,才恍然大悟。
pop (%esp)终归是一个内存操作指令,需要地址线和数据线。
pop (%esp)的微码操作:
1、将(%esp)送到数据线上
2、将%esp+4,送到地址线上
3、接收到Ready信号,写内存
也就是说这完全没有违背我们所学的计算机理论,只不过凑巧pop指令和%esp寄存器凑到了一块。
从字面上理解这条指令的意思:将栈顶pop到栈顶,不过栈顶在pop之后发生了+4操作,也就是说将(%esp)移动到了4(%esp)。
如果单单是普通的pop指令的话,这是没有问题的;如果是movl $0x1234, %esp的话也是没有问题的。
pop (%eax)的话:
1、将(%esp)送到数据线上
2、将%esp+4
3、将(%eax),送到地址线上
4、接收到Ready信号,写内存
也就是说pop和push操作都是要加减%esp的,如果是要操作内存的话,加减esp操作要早于写内存操作。为什么会这样子,只能够问intel了。
BTW:我认为CPU可以先写内存再加减esp。
顺便练手gnu的内嵌汇编:
#include<stdio.h>
int main() { int temp = 0x1234; int temp0,temp1,temp2,temp3;
__asm__ __volatile__("push %4\n\t" "movl 4(%%esp),%0\n\t" "movl -4(%%esp),%1\n\t" "pop (%%esp)\n\t" "movl (%%esp),%2\n\t" "movl -4(%%esp),%3\n\t" :"=r"(temp0),"=r"(temp1),"=r"(temp2),"=r"(temp3) :"g"(temp));
printf("---------------\n"); printf("esp+4:0x%08x\n", temp0); printf("esp:0x%08x\n", temp); printf("esp-4:0x%08x\n", temp1); printf("----- pop -----\n"); printf("esp:0x%08x\n", temp2); printf("esp-4:0x%08x\n", temp3);
return 0; }
|