1.从C到汇编的转变。
先编写一个简单的C程序,main.c, 源码内容如下:
-
#include <stdio.h>
-
-
int g(int a)
-
{
-
return a + 3;
-
}
-
int main(int argc, char *argv[])
-
{
-
return g(4);
-
}
2.通过命令:gcc main.c -S -o main.s ,将源文件编译成 .S 文件。打开后内容如下:(以 . 开头的指令行为注释行,不参与实际的运行,为查看方便,已经去掉此内容)
-
g:
-
pushl %ebp
-
movl %esp, %ebp
-
movl 8(%ebp), %eax
-
addl $3, %eax
-
popl %ebp
-
ret
-
main:
-
pushl %ebp
-
movl %esp, %ebp
-
subl $4, %esp
-
movl $4, (%esp)
-
call g
-
leave
-
ret
3.指令执行分析:
a.在此先将列出几个特殊指令的形态:(栈空间由高地址向低地址增长)
pushl %eax : subl $4, %esp ; movl %eax, %(%esp); #将eax寄存器的数进行压栈 ,esp下移4个字节,然后将eax的值赋给esp。
popl %eax : movl (%esp), %eax ; subl %esp; #将esp内的值赋给eax寄存器,同时将esp的指针上移四个字节。
call 0x1234 : pushl %eip(*) ; movl 0x1234 , %eip(*); #将当前eip的值压栈,然后将新的入口地址0x1234放入eip寄存器中。
ret : popl %eip(*); #将下一行要处理的指令行号送入eip寄存器中。
enter : pushl %ebp; movl %esp, %ebp ; #将栈基指针压栈,然后将栈顶指针赋给栈基指针。
leave : movl %ebp,%esp ; popl %ebp ; #这个跟enter正好相反。
b.程序的入口函数是main,在汇编代码中行号为 8. 第9/10行就是上面写的enter指令,这时重新构造了空栈,esp == ebp。
-
subl $4, %esp
-
movl $4, (%esp)
这两句先将esp 向下增长4个字节,然后将立即数4 赋给当前的esp。
调用call时,eip指向的是下一条指令,行号为14. call指令会将eip进行压栈,然后将g 的行号放到eip中。此后程序会进入到g 函数中继续执行。
第2/3 行进行构造栈。
下一步中第4/5/6行进行对eax赋值。
-
movl 8(%ebp), %eax #将ebp 地址加8 ,也就是movl $4, (%esp) 指令的 4 取出,放入eax中
-
addl $3, %eax # 将3 加到eax中,此时eax值为7。
-
popl %ebp #将当前栈指针内容赋给ebp。
下一步第7行:
-
ret #将下一行要处理的指令行号送入eip寄存器中。
下一步进入第14行,执行leave以及ret指令。清空当前栈。此时整个程序的返回值保存在eax中,结果为7。整个程序运行结束。
由上面的分析可以看出,整个汇编指令都是以流水线方式逐条进行处理,这也说明了cpu主频其实跟计算机的处理速度是有很大关联的。
作者程大鹏, 转载请注明出处 http://blog.chinaunix.net/blog/post.html
Linux内核分析》MOOC课程 ”
阅读(1893) | 评论(0) | 转发(0) |